envio 3.15.0 → 3.16.0-subgraph
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +40 -34
- package/evm.schema.json +11 -2
- package/fuel.schema.json +10 -1
- package/index.d.ts +6 -0
- package/licenses/README.md +1 -1
- package/package.json +10 -9
- package/src/Batch.res.mjs +5 -5
- package/src/BatchProcessing.res +1 -0
- package/src/BatchProcessing.res.mjs +1 -0
- package/src/Bin.res +7 -0
- package/src/Bin.res.mjs +8 -0
- package/src/ChainFetching.res +450 -16
- package/src/ChainFetching.res.mjs +403 -32
- package/src/ChainState.res +124 -7
- package/src/ChainState.res.mjs +128 -67
- package/src/ChainState.resi +26 -0
- package/src/Clock.res +57 -0
- package/src/Clock.res.mjs +72 -0
- package/src/Config.res +31 -3
- package/src/Config.res.mjs +15 -4
- package/src/ContractRegisterContext.res +3 -6
- package/src/ContractRegisterContext.res.mjs +4 -2
- package/src/Core.res +95 -36
- package/src/Core.res.mjs +90 -1
- package/src/CrossChainState.res +12 -2
- package/src/CrossChainState.res.mjs +3 -3
- package/src/Ecosystem.res +7 -4
- package/src/Ecosystem.res.mjs +8 -8
- package/src/EffectCacheLookup.res +98 -0
- package/src/EffectCacheLookup.res.mjs +116 -0
- package/src/EffectState.res +10 -2
- package/src/EffectState.res.mjs +8 -4
- package/src/EffectState.resi +3 -1
- package/src/EntityId.res +2 -7
- package/src/EntityId.res.mjs +1 -8
- package/src/EntityTables.res +4 -1
- package/src/EntityTables.res.mjs +1 -1
- package/src/Env.res +20 -0
- package/src/Env.res.mjs +16 -0
- package/src/EventProcessing.res +38 -1
- package/src/EventProcessing.res.mjs +28 -1
- package/src/FetchState.res +408 -73
- package/src/FetchState.res.mjs +361 -91
- package/src/FinalizeBackfill.res +2 -4
- package/src/FinalizeBackfill.res.mjs +3 -4
- package/src/HandlerLoader.res +39 -15
- package/src/HandlerLoader.res.mjs +27 -8
- package/src/HandlerRegister.res +4 -0
- package/src/HandlerRegister.res.mjs +5 -2
- package/src/HandlerRegister.resi +2 -0
- package/src/InMemoryTable.res +17 -16
- package/src/InMemoryTable.res.mjs +14 -15
- package/src/IndexerState.res +28 -4
- package/src/IndexerState.res.mjs +19 -6
- package/src/IndexerState.resi +4 -0
- package/src/Internal.res +61 -49
- package/src/Internal.res.mjs +36 -80
- package/src/LoadLayer.res +54 -15
- package/src/LoadLayer.res.mjs +75 -45
- package/src/LoadLayer.resi +26 -0
- package/src/Main.res +14 -1
- package/src/Main.res.mjs +4 -3
- package/src/Persistence.res +3 -23
- package/src/Persistence.res.mjs +3 -3
- package/src/PreloadLookahead.res +202 -0
- package/src/PreloadLookahead.res.mjs +218 -0
- package/src/Rollback.res +0 -1
- package/src/Rollback.res.mjs +1 -1
- package/src/RunScope.res +3 -0
- package/src/SafeCheckpointTracking.res +4 -4
- package/src/SafeCheckpointTracking.res.mjs +1 -1
- package/src/SimulateItems.res +42 -26
- package/src/SimulateItems.res.mjs +40 -16
- package/src/StagedRows.res +106 -0
- package/src/StagedRows.res.mjs +102 -0
- package/src/Staging.res +32 -14
- package/src/Staging.res.mjs +17 -20
- package/src/Storage.res +303 -948
- package/src/Storage.res.mjs +229 -708
- package/src/SubgraphLog.res +19 -0
- package/src/SubgraphLog.res.mjs +26 -0
- package/src/Supervisor.res +75 -56
- package/src/Supervisor.res.mjs +55 -26
- package/src/SyncWrites.res +107 -0
- package/src/SyncWrites.res.mjs +98 -0
- package/src/TestIndexer.res +22 -27
- package/src/TestIndexer.res.mjs +20 -17
- package/src/Throttler.res +15 -8
- package/src/Throttler.res.mjs +9 -6
- package/src/Throttler.resi +1 -1
- package/src/UserContext.res +419 -46
- package/src/UserContext.res.mjs +318 -30
- package/src/Utils.res +17 -6
- package/src/Utils.res.mjs +12 -6
- package/src/Writing.res +0 -1
- package/src/Writing.res.mjs +1 -1
- package/src/bindings/ClickHouse.res +0 -322
- package/src/bindings/ClickHouse.res.mjs +0 -338
- package/src/bindings/StorageClient.res +108 -174
- package/src/bindings/StorageClient.res.mjs +19 -8
- package/src/bindings/Vitest.res +0 -26
- package/src/bindings/Vitest.res.mjs +0 -3
- package/src/db/Table.res +24 -150
- package/src/db/Table.res.mjs +38 -272
- package/src/sources/AddressSet.res +2 -0
- package/src/sources/AddressStore.res +3 -0
- package/src/sources/CallSource.res +274 -0
- package/src/sources/CallSource.res.mjs +196 -0
- package/src/sources/ChainSources.res +2 -0
- package/src/sources/ChainSources.res.mjs +2 -2
- package/src/sources/EvmChain.res +10 -1
- package/src/sources/EvmChain.res.mjs +6 -3
- package/src/sources/EvmHyperSyncSource.res +2 -2
- package/src/sources/EvmHyperSyncSource.res.mjs +1 -1
- package/src/sources/EvmRpcClient.res +13 -3
- package/src/sources/EvmRpcClient.res.mjs +4 -3
- package/src/sources/EvmRpcWs.res +4 -3
- package/src/sources/EvmRpcWs.res.mjs +2 -2
- package/src/sources/HeightFeed.res +31 -10
- package/src/sources/HeightFeed.res.mjs +10 -6
- package/src/sources/HeightStream.res +32 -17
- package/src/sources/HeightStream.res.mjs +10 -10
- package/src/sources/HyperSync.res +2 -1
- package/src/sources/HyperSync.res.mjs +2 -1
- package/src/sources/HyperSyncSSE.res +4 -6
- package/src/sources/HyperSyncSSE.res.mjs +2 -2
- package/src/sources/RpcSource.res +10 -1
- package/src/sources/RpcSource.res.mjs +4 -3
- package/src/sources/Source.res +3 -0
- package/src/sources/SourceManager.res +84 -77
- package/src/sources/SourceManager.res.mjs +17 -20
- package/src/sources/SourceManager.resi +3 -4
- package/src/sources/StartBlockResolver.res +1 -0
- package/src/sources/StartBlockResolver.res.mjs +3 -2
- package/src/sources/SvmHyperSyncSource.res +2 -2
- package/src/sources/SvmHyperSyncSource.res.mjs +1 -1
- package/src/subgraph/abi-types.ts +84 -0
- package/src/subgraph/aggregations.ts +745 -0
- package/src/subgraph/as-package.ts +143 -0
- package/src/subgraph/assemblyscript-regex.ts +67 -0
- package/src/subgraph/assemblyscript.ts +731 -0
- package/src/subgraph/blocks.ts +398 -0
- package/src/subgraph/calls-source.ts +702 -0
- package/src/subgraph/calls.ts +258 -0
- package/src/subgraph/conformance.ts +106 -0
- package/src/subgraph/errors.ts +85 -0
- package/src/subgraph/graft.ts +73 -0
- package/src/subgraph/graph-cli-worker.mjs +35 -0
- package/src/subgraph/graph-cli.ts +900 -0
- package/src/subgraph/graph-node-decimal.ts +199 -0
- package/src/subgraph/graph-ts-enums.ts +28 -0
- package/src/subgraph/graph-ts-types/VERSION +2 -0
- package/src/subgraph/graph-ts-types/chain/arweave.d.ts +70 -0
- package/src/subgraph/graph-ts-types/chain/cosmos.d.ts +327 -0
- package/src/subgraph/graph-ts-types/chain/ethereum.d.ts +233 -0
- package/src/subgraph/graph-ts-types/chain/near.d.ts +253 -0
- package/src/subgraph/graph-ts-types/chain/starknet.d.ts +32 -0
- package/src/subgraph/graph-ts-types/common/collections.d.ts +136 -0
- package/src/subgraph/graph-ts-types/common/conversion.d.ts +11 -0
- package/src/subgraph/graph-ts-types/common/datasource.d.ts +30 -0
- package/src/subgraph/graph-ts-types/common/eager-offset.d.ts +0 -0
- package/src/subgraph/graph-ts-types/common/json.d.ts +17 -0
- package/src/subgraph/graph-ts-types/common/numbers.d.ts +120 -0
- package/src/subgraph/graph-ts-types/common/value.d.ts +120 -0
- package/src/subgraph/graph-ts-types/common/yaml.d.ts +90 -0
- package/src/subgraph/graph-ts-types/global/global.d.ts +194 -0
- package/src/subgraph/graph-ts-types/helper-functions.d.ts +22 -0
- package/src/subgraph/graph-ts-types/index.d.ts +102 -0
- package/src/subgraph/graph-ts.ts +4339 -0
- package/src/subgraph/hosts.ts +554 -0
- package/src/subgraph/log.ts +28 -0
- package/src/subgraph/receipts.ts +184 -0
- package/src/subgraph/register-slots.ts +93 -0
- package/src/subgraph/rollup.ts +109 -0
- package/src/subgraph/rpc.ts +337 -0
- package/src/subgraph/runtime.ts +2059 -0
- package/src/subgraph/scope.ts +176 -0
- package/src/subgraph/stats.ts +42 -0
- package/src/subgraph/template-instances.ts +66 -0
- package/src/subgraph/toolchain.ts +327 -0
- package/src/subgraph/transient.ts +176 -0
- package/svm.schema.json +11 -2
- package/src/PgWriting.res +0 -36
- package/src/PgWriting.res.mjs +0 -34
- package/src/db/IndexDefinition.res +0 -47
- package/src/db/IndexDefinition.res.mjs +0 -67
package/src/UserContext.res
CHANGED
|
@@ -1,3 +1,91 @@
|
|
|
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
|
+
|
|
1
89
|
// What every handler run of an indexer shares. The run itself — its item,
|
|
2
90
|
// checkpoint and whether it's a preload — is read from the RunScope it was
|
|
3
91
|
// entered with, so one context object serves every run, concurrent ones and
|
|
@@ -58,7 +146,7 @@ let isCrossChain = (params: params, effect: Internal.effect) =>
|
|
|
58
146
|
|
|
59
147
|
// A chain-scoped effect always resolves against the chain of the handler run
|
|
60
148
|
// that triggered the call, even several effects deep.
|
|
61
|
-
let rec
|
|
149
|
+
let rec prepareEffectCall = (
|
|
62
150
|
params: params,
|
|
63
151
|
~run: RunScope.handlerRun,
|
|
64
152
|
~caller: option<Internal.effect>,
|
|
@@ -88,23 +176,73 @@ let rec callEffect = (
|
|
|
88
176
|
~callEffect=(child, input) => params->callEffect(~run, ~caller=Some(effect), child, input),
|
|
89
177
|
)
|
|
90
178
|
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)
|
|
91
194
|
LoadLayer.loadEffect(
|
|
92
195
|
~loadManager=params.loadManager,
|
|
93
196
|
~persistence=params.persistence,
|
|
94
197
|
~effect,
|
|
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
|
-
},
|
|
198
|
+
~effectArgs,
|
|
102
199
|
~scope,
|
|
103
200
|
~indexerState=params.indexerState,
|
|
104
201
|
~shouldGroup=run.isPreload,
|
|
105
202
|
)
|
|
106
203
|
}
|
|
107
204
|
|
|
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
|
+
|
|
108
246
|
// A TTL drops an entity's rows whenever ClickHouse merges them, so for one kept
|
|
109
247
|
// there alone a read would turn on merge timing rather than on the events.
|
|
110
248
|
let expiresUnread = (entityConfig: Internal.entityConfig) =>
|
|
@@ -129,8 +267,15 @@ type entityContext = {
|
|
|
129
267
|
deleteUnsafe: EntityId.t => unit,
|
|
130
268
|
}
|
|
131
269
|
|
|
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
|
+
|
|
132
276
|
let makeEntityContext = (params: params, ~entityConfig: Internal.entityConfig) => {
|
|
133
277
|
let prop = entityConfig.name
|
|
278
|
+
let entityName = entityConfig.name
|
|
134
279
|
// The handler context is always chain-scoped, so a per-chain entity resolves
|
|
135
280
|
// to the chain the handler runs on and a cross-chain one to the shared
|
|
136
281
|
// partition.
|
|
@@ -138,7 +283,7 @@ let makeEntityContext = (params: params, ~entityConfig: Internal.entityConfig) =
|
|
|
138
283
|
entityConfig->InMemoryStore.entityScope(~chainId=run.item->Internal.getItemChainId)
|
|
139
284
|
|
|
140
285
|
let loadById = entityId => {
|
|
141
|
-
let run =
|
|
286
|
+
let run = currentRunOrThrow(~prop)
|
|
142
287
|
LoadLayer.loadById(
|
|
143
288
|
~loadManager=params.loadManager,
|
|
144
289
|
~persistence=params.persistence,
|
|
@@ -150,35 +295,34 @@ let makeEntityContext = (params: params, ~entityConfig: Internal.entityConfig) =
|
|
|
150
295
|
)
|
|
151
296
|
}
|
|
152
297
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
->
|
|
158
|
-
|
|
159
|
-
change
|
|
160
|
-
)
|
|
161
|
-
|
|
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
|
+
}
|
|
162
311
|
|
|
163
|
-
// A preload run's writes are no-ops: its run proper makes them.
|
|
164
312
|
let set = (entity: Internal.entity) => {
|
|
165
|
-
let run =
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
)
|
|
174
|
-
}
|
|
313
|
+
let run = currentRunOrThrow(~prop)
|
|
314
|
+
run->write(
|
|
315
|
+
Set({
|
|
316
|
+
entityId: entity.id->EntityId.unsafeOfString,
|
|
317
|
+
checkpointId: run.checkpointId,
|
|
318
|
+
entity,
|
|
319
|
+
}),
|
|
320
|
+
)
|
|
175
321
|
}
|
|
176
322
|
|
|
177
323
|
let deleteUnsafe = entityId => {
|
|
178
|
-
let run =
|
|
179
|
-
|
|
180
|
-
run->write(Delete({entityId, checkpointId: run.checkpointId}))
|
|
181
|
-
}
|
|
324
|
+
let run = currentRunOrThrow(~prop)
|
|
325
|
+
run->write(Delete({entityId, checkpointId: run.checkpointId}))
|
|
182
326
|
}
|
|
183
327
|
|
|
184
328
|
let entityContext: entityContext = if entityConfig->expiresUnread {
|
|
@@ -194,7 +338,7 @@ let makeEntityContext = (params: params, ~entityConfig: Internal.entityConfig) =
|
|
|
194
338
|
{
|
|
195
339
|
get: loadById,
|
|
196
340
|
getWhere: filter => {
|
|
197
|
-
let run =
|
|
341
|
+
let run = currentRunOrThrow(~prop)
|
|
198
342
|
LoadLayer.loadByFilter(
|
|
199
343
|
~loadManager=params.loadManager,
|
|
200
344
|
~persistence=params.persistence,
|
|
@@ -231,11 +375,207 @@ let makeEntityContext = (params: params, ~entityConfig: Internal.entityConfig) =
|
|
|
231
375
|
}
|
|
232
376
|
}
|
|
233
377
|
|
|
234
|
-
|
|
235
|
-
Utils.StrictObject.
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
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
|
+
// The lookahead keys an entity as its writes do, a Bytes id by its hex.
|
|
394
|
+
let entityKey = entityConfig.table.entityIdToKey(entityId->EntityId.unsafeOfString)
|
|
395
|
+
switch sync.writes->SyncWrites.getById(~entityConfig, ~entityId) {
|
|
396
|
+
| Some(entity) => entity
|
|
397
|
+
| None =>
|
|
398
|
+
switch run.isPreload
|
|
399
|
+
? PreloadLookahead.overlayGet(~item=run.item, ~entityName, ~scope, ~entityId=entityKey)
|
|
400
|
+
: None {
|
|
401
|
+
| Some(entity) => entity
|
|
402
|
+
| None =>
|
|
403
|
+
switch LoadLayer.getByIdInMemory(
|
|
404
|
+
~entityConfig,
|
|
405
|
+
~scope,
|
|
406
|
+
~indexerState=params.indexerState,
|
|
407
|
+
~entityId,
|
|
408
|
+
) {
|
|
409
|
+
| Some(entity) =>
|
|
410
|
+
if run.isPreload && entity->Option.isNone {
|
|
411
|
+
PreloadLookahead.recordAbsent(
|
|
412
|
+
~item=run.item,
|
|
413
|
+
~entityName,
|
|
414
|
+
~scope,
|
|
415
|
+
~entityId=entityKey,
|
|
416
|
+
)
|
|
417
|
+
}
|
|
418
|
+
entity
|
|
419
|
+
| None =>
|
|
420
|
+
sync->missOrSuspend(
|
|
421
|
+
~absent=None,
|
|
422
|
+
LoadLayer.loadById(
|
|
423
|
+
~loadManager=params.loadManager,
|
|
424
|
+
~persistence=params.persistence,
|
|
425
|
+
~entityConfig,
|
|
426
|
+
~scope,
|
|
427
|
+
~indexerState=params.indexerState,
|
|
428
|
+
~shouldGroup=run.isPreload,
|
|
429
|
+
~entityId,
|
|
430
|
+
),
|
|
431
|
+
)
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
},
|
|
436
|
+
getWhereSync: filter => {
|
|
437
|
+
let run = currentRunOrThrow(~prop)
|
|
438
|
+
let sync = run->syncOrThrow(~prop=`${prop}.getWhereSync`)
|
|
439
|
+
let scope = run->scopeOf
|
|
440
|
+
let filter = filter->EntityFilter.parseOrThrow(~entityName, ~table=entityConfig.table)
|
|
441
|
+
switch LoadLayer.getByFilterInMemory(
|
|
442
|
+
~entityConfig,
|
|
443
|
+
~scope,
|
|
444
|
+
~indexerState=params.indexerState,
|
|
445
|
+
~filter,
|
|
446
|
+
) {
|
|
447
|
+
| Some(entities) => sync.writes->SyncWrites.overlayFilter(entities, ~entityConfig, ~filter)
|
|
448
|
+
| None =>
|
|
449
|
+
sync->missOrSuspend(
|
|
450
|
+
~absent=[],
|
|
451
|
+
LoadLayer.loadByParsedFilter(
|
|
452
|
+
~loadManager=params.loadManager,
|
|
453
|
+
~persistence=params.persistence,
|
|
454
|
+
~entityConfig,
|
|
455
|
+
~scope,
|
|
456
|
+
~indexerState=params.indexerState,
|
|
457
|
+
~shouldGroup=run.isPreload,
|
|
458
|
+
~filter,
|
|
459
|
+
),
|
|
460
|
+
)
|
|
461
|
+
}
|
|
462
|
+
},
|
|
463
|
+
// Never suspends: the in-memory table spans the whole batch, so a change
|
|
464
|
+
// only counts as "in this block" when it was written at this run's
|
|
465
|
+
// checkpoint.
|
|
466
|
+
getInBlockSync: entityId => {
|
|
467
|
+
let run = currentRunOrThrow(~prop)
|
|
468
|
+
let sync = run->syncOrThrow(~prop=`${prop}.getInBlockSync`)
|
|
469
|
+
switch sync.writes->SyncWrites.getById(~entityConfig, ~entityId) {
|
|
470
|
+
| Some(entity) => entity
|
|
471
|
+
| None =>
|
|
472
|
+
let inMemTable =
|
|
473
|
+
params.indexerState->InMemoryStore.getInMemTable(~entityConfig, ~scope=run->scopeOf)
|
|
474
|
+
switch inMemTable.latestEntityChangeById->Utils.Dict.dangerouslyGetNonOption(
|
|
475
|
+
inMemTable.entityIdToKey(entityId->EntityId.unsafeOfString),
|
|
476
|
+
) {
|
|
477
|
+
| Some(change) if change->Change.getCheckpointId == run.checkpointId =>
|
|
478
|
+
change->InMemoryTable.Entity.mapChangeToEntity
|
|
479
|
+
| _ => None
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
},
|
|
483
|
+
}
|
|
484
|
+
object->Utils.StrictObject.assign(
|
|
485
|
+
syncEntityContext->(Utils.magic: syncEntityContext => entityContext),
|
|
486
|
+
)
|
|
487
|
+
} else {
|
|
488
|
+
object
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
// Deterministic mappings always make progress, so the cap only exists to turn
|
|
493
|
+
// a non-deterministic one into a clear error instead of a hang.
|
|
494
|
+
let maxSyncRounds = 10000
|
|
495
|
+
|
|
496
|
+
let rec runSyncRound = async (params: params, sync: syncRun, fn: unit => unit, ~round) => {
|
|
497
|
+
if round > maxSyncRounds {
|
|
498
|
+
JsError.throwWithMessage(
|
|
499
|
+
`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.`,
|
|
500
|
+
)
|
|
501
|
+
}
|
|
502
|
+
let run = sync.run
|
|
503
|
+
sync.status = Active
|
|
504
|
+
sync.pending = []
|
|
505
|
+
sync.speculated = 0
|
|
506
|
+
sync.writes = SyncWrites.make()
|
|
507
|
+
let startedAt = PreloadLookahead.roundStarted(~isPreload=run.isPreload)
|
|
508
|
+
|
|
509
|
+
let previous = currentSync.contents
|
|
510
|
+
currentSync := Some(sync)
|
|
511
|
+
let outcome = try {
|
|
512
|
+
fn()
|
|
513
|
+
Ok()
|
|
514
|
+
} catch {
|
|
515
|
+
| exn => Error(exn)
|
|
516
|
+
}
|
|
517
|
+
currentSync := previous
|
|
518
|
+
PreloadLookahead.roundEnded(~item=run.item, ~startedAt)
|
|
519
|
+
let writes = sync.writes
|
|
520
|
+
|
|
521
|
+
// Neither a suspended round nor a failed one keeps what it wrote.
|
|
522
|
+
sync.writes = SyncWrites.make()
|
|
523
|
+
|
|
524
|
+
let suspended = switch outcome {
|
|
525
|
+
| Ok() =>
|
|
526
|
+
switch sync.status {
|
|
527
|
+
// The body caught the suspend and returned anyway, so it didn't see the
|
|
528
|
+
// reads it asked for: it is replayed like any other suspended round.
|
|
529
|
+
| Aborted(_) => true
|
|
530
|
+
// Ran on reads it was told were absent.
|
|
531
|
+
| Active if sync.speculated > 0 => true
|
|
532
|
+
| Active =>
|
|
533
|
+
if run.isPreload {
|
|
534
|
+
writes->SyncWrites.forEachChange((~entityConfig, ~scope, change) =>
|
|
535
|
+
PreloadLookahead.overlaySet(
|
|
536
|
+
~item=run.item,
|
|
537
|
+
~entityName=entityConfig.name,
|
|
538
|
+
~scope,
|
|
539
|
+
~entityId=entityConfig.table.entityIdToKey(change->Change.getEntityId),
|
|
540
|
+
~entity=change->InMemoryTable.Entity.mapChangeToEntity,
|
|
541
|
+
)
|
|
542
|
+
)
|
|
543
|
+
} else {
|
|
544
|
+
writes->SyncWrites.commit(~indexerState=params.indexerState)
|
|
545
|
+
}
|
|
546
|
+
false
|
|
547
|
+
}
|
|
548
|
+
// A round handed a speculated absence may well throw on it: the replay that
|
|
549
|
+
// sees the real value decides whether the error is the handler's.
|
|
550
|
+
| Error(exn) if exn->isSuspend || sync.speculated > 0 => true
|
|
551
|
+
| Error(exn) =>
|
|
552
|
+
// The body threw its own error after scheduling a read. Those ops are
|
|
553
|
+
// nobody's result now, and an unhandled rejection would replace the error
|
|
554
|
+
// the handler actually raised.
|
|
555
|
+
sync.pending->Array.forEach(promise => promise->Utils.Promise.catchResolve(_ => ())->ignore)
|
|
556
|
+
sync.pending = []
|
|
557
|
+
throw(exn)
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
if suspended {
|
|
561
|
+
// A round only suspends by scheduling the read it missed.
|
|
562
|
+
let pending = sync.pending
|
|
563
|
+
sync.pending = []
|
|
564
|
+
let errors = []
|
|
565
|
+
let _ = await pending
|
|
566
|
+
->Array.map(promise =>
|
|
567
|
+
promise->Promise.catch(exn => {
|
|
568
|
+
errors->Array.push(exn)
|
|
569
|
+
Promise.resolve()
|
|
570
|
+
})
|
|
571
|
+
)
|
|
572
|
+
->Promise.all
|
|
573
|
+
switch errors->Array.get(0) {
|
|
574
|
+
| Some(exn) => throw(exn)
|
|
575
|
+
| None => ()
|
|
576
|
+
}
|
|
577
|
+
await params->runSyncRound(sync, fn, ~round=round + 1)
|
|
578
|
+
}
|
|
239
579
|
}
|
|
240
580
|
|
|
241
581
|
// One context for every handler run of an indexer.
|
|
@@ -251,7 +591,7 @@ let makeHandlerContext = (params: params): Internal.handlerContext => {
|
|
|
251
591
|
context
|
|
252
592
|
->Utils.Object.defineProperty(
|
|
253
593
|
name,
|
|
254
|
-
{enumerable: true, get: () => get(
|
|
594
|
+
{enumerable: true, get: () => get(currentRunOrThrow(~prop=name))},
|
|
255
595
|
)
|
|
256
596
|
->ignore
|
|
257
597
|
|
|
@@ -263,12 +603,7 @@ let makeHandlerContext = (params: params): Internal.handlerContext => {
|
|
|
263
603
|
// Reads the run when called, so a kept `context.effect` is refused once its
|
|
264
604
|
// handler resolved, like a kept entity accessor.
|
|
265
605
|
let effect = (effect, input) =>
|
|
266
|
-
params->callEffect(
|
|
267
|
-
~run=RunScope.currentHandlerRunOrThrow(~prop="effect"),
|
|
268
|
-
~caller=None,
|
|
269
|
-
effect,
|
|
270
|
-
input,
|
|
271
|
-
)
|
|
606
|
+
params->callEffect(~run=currentRunOrThrow(~prop="effect"), ~caller=None, effect, input)
|
|
272
607
|
context
|
|
273
608
|
->Utils.Object.definePropertyWithValue(
|
|
274
609
|
"effect",
|
|
@@ -282,6 +617,44 @@ let makeHandlerContext = (params: params): Internal.handlerContext => {
|
|
|
282
617
|
},
|
|
283
618
|
)
|
|
284
619
|
->ignore
|
|
620
|
+
if Env.subgraphMode.contents {
|
|
621
|
+
context
|
|
622
|
+
->Utils.Object.definePropertyWithValue(
|
|
623
|
+
"effectSync",
|
|
624
|
+
{
|
|
625
|
+
enumerable: true,
|
|
626
|
+
value: ((effect, input) => params->effectSync(effect, input))->(
|
|
627
|
+
Utils.magic: ((Internal.effect, Internal.effectInput) => Internal.effectOutput) => unknown
|
|
628
|
+
),
|
|
629
|
+
},
|
|
630
|
+
)
|
|
631
|
+
->ignore
|
|
632
|
+
context
|
|
633
|
+
->Utils.Object.definePropertyWithValue(
|
|
634
|
+
"runSync",
|
|
635
|
+
{
|
|
636
|
+
enumerable: true,
|
|
637
|
+
value: (
|
|
638
|
+
fn => {
|
|
639
|
+
let run = currentRunOrThrow(~prop="runSync")
|
|
640
|
+
params->runSyncRound(
|
|
641
|
+
{
|
|
642
|
+
run,
|
|
643
|
+
status: Active,
|
|
644
|
+
pending: [],
|
|
645
|
+
memo: Dict.make(),
|
|
646
|
+
writes: SyncWrites.make(),
|
|
647
|
+
speculated: 0,
|
|
648
|
+
},
|
|
649
|
+
fn,
|
|
650
|
+
~round=1,
|
|
651
|
+
)
|
|
652
|
+
}
|
|
653
|
+
)->(Utils.magic: ((unit => unit) => promise<unit>) => unknown),
|
|
654
|
+
},
|
|
655
|
+
)
|
|
656
|
+
->ignore
|
|
657
|
+
}
|
|
285
658
|
define("isPreload", run => run.isPreload->(Utils.magic: bool => unknown))
|
|
286
659
|
define("chain", run =>
|
|
287
660
|
params.chains
|