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.
- package/evm.schema.json +1 -1
- package/package.json +6 -6
- package/src/Config.res +0 -9
- package/src/Config.res.mjs +2 -6
- package/src/Core.res +0 -21
- package/src/Core.res.mjs +0 -17
- package/src/EventProcessing.res +6 -6
- package/src/EventProcessing.res.mjs +6 -8
- package/src/HandlerLoader.res +22 -48
- package/src/HandlerLoader.res.mjs +15 -41
- package/src/HandlerTypeCheck.res +207 -0
- package/src/HandlerTypeCheck.res.mjs +262 -0
- package/src/LoadLayer.res +7 -39
- package/src/LoadLayer.res.mjs +38 -65
- package/src/LoadLayer.resi +0 -28
- package/src/PgStorage.res +5 -24
- package/src/PgStorage.res.mjs +1 -18
- package/src/UserContext.res +62 -377
- package/src/UserContext.res.mjs +39 -297
- package/src/bindings/NodeJs.res +31 -0
- package/src/bindings/NodeJs.res.mjs +3 -0
- package/src/db/Table.res +0 -10
- package/src/db/Table.res.mjs +11 -22
- package/src/sources/EvmRpcClient.res +2 -0
- package/src/sources/RpcSource.res +17 -4
- package/src/sources/RpcSource.res.mjs +23 -5
- package/src/sources/SourceManager.res +63 -40
- package/src/sources/SourceManager.res.mjs +32 -20
- package/src/HandlerTypeCheck.mjs +0 -14
- package/src/HandlerTypeCheckWorker.mjs +0 -68
- package/src/SyncWrites.res +0 -86
- package/src/SyncWrites.res.mjs +0 -85
- package/src/subgraph/abi-types.ts +0 -63
- package/src/subgraph/assemblyscript.ts +0 -85
- package/src/subgraph/blocks.ts +0 -183
- package/src/subgraph/calls.ts +0 -149
- package/src/subgraph/conformance.ts +0 -83
- package/src/subgraph/errors.ts +0 -96
- package/src/subgraph/graph-cli-worker.mjs +0 -26
- package/src/subgraph/graph-cli.ts +0 -190
- package/src/subgraph/graph-ts-enums.ts +0 -28
- 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 -2285
- package/src/subgraph/hosts.ts +0 -158
- package/src/subgraph/rpc.ts +0 -65
- package/src/subgraph/runtime.ts +0 -595
- 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
|
|
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
|
|
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:
|
|
720
|
-
|
|
721
|
-
|
|
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,
|
package/src/PgStorage.res.mjs
CHANGED
|
@@ -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:
|
|
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
|
};
|
package/src/UserContext.res
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
135
|
-
|
|
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
|
|
151
|
-
effect
|
|
152
|
-
? Internal.CrossChain
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
~
|
|
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
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
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
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
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
|
-
|
|
333
|
+
isResolved: params.isResolved,
|
|
649
334
|
config: params.config,
|
|
650
335
|
entityConfig,
|
|
651
336
|
}
|