envio 3.3.0-alpha.11 → 3.3.0-alpha.13
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/index.d.ts +19 -0
- package/package.json +6 -6
- package/src/ChainFetching.res +21 -12
- package/src/ChainFetching.res.mjs +20 -9
- package/src/ChainState.res +20 -8
- package/src/ChainState.res.mjs +13 -12
- package/src/ChainState.resi +1 -1
- package/src/CrossChainState.res +64 -41
- package/src/CrossChainState.res.mjs +18 -13
- package/src/EffectState.res +100 -0
- package/src/EffectState.res.mjs +74 -0
- package/src/EffectState.resi +32 -0
- package/src/Envio.res +25 -7
- package/src/Envio.res.mjs +15 -19
- package/src/FetchState.res +115 -76
- package/src/FetchState.res.mjs +114 -104
- package/src/InMemoryStore.res +14 -26
- package/src/InMemoryStore.res.mjs +6 -19
- package/src/IndexerState.res +10 -19
- package/src/IndexerState.res.mjs +7 -6
- package/src/IndexerState.resi +1 -9
- package/src/Internal.res +87 -12
- package/src/Internal.res.mjs +96 -2
- package/src/LoadLayer.res +44 -24
- package/src/LoadLayer.res.mjs +43 -20
- package/src/LoadLayer.resi +1 -0
- package/src/Persistence.res +12 -3
- package/src/PgStorage.res +155 -82
- package/src/PgStorage.res.mjs +130 -77
- package/src/Prometheus.res +20 -10
- package/src/Prometheus.res.mjs +22 -10
- package/src/Rollback.res +32 -13
- package/src/Rollback.res.mjs +24 -11
- package/src/UserContext.res +62 -23
- package/src/UserContext.res.mjs +26 -6
- package/src/Writing.res +28 -12
- package/src/Writing.res.mjs +15 -7
- package/src/bindings/NodeJs.res +5 -0
- package/src/sources/SvmHyperSyncSource.res +10 -0
- package/src/sources/SvmHyperSyncSource.res.mjs +1 -1
package/src/UserContext.res
CHANGED
|
@@ -28,10 +28,23 @@ Utils.Object.defineProperty(
|
|
|
28
28
|
},
|
|
29
29
|
)
|
|
30
30
|
%%raw(`
|
|
31
|
-
|
|
31
|
+
// Top-level so the getter is created once, not per context. \`this\` is the
|
|
32
|
+
// EffectContext instance; only cross-chain contexts install it.
|
|
33
|
+
function throwCrossChainChainAccess() {
|
|
34
|
+
throw new Error('context.chain is not available on the cross-chain effect "' + this._effectName + '". Set \`crossChain: false\` in its options to scope the effect to a single chain, then read context.chain.id.');
|
|
35
|
+
}
|
|
36
|
+
var crossChainChainDescriptor = { get: throwCrossChainChainAccess, enumerable: true };
|
|
37
|
+
var EffectContext = function(params, chainId, effectName, defaultShouldCache, callEffect) {
|
|
32
38
|
paramsByThis.set(this, params);
|
|
33
39
|
this.effect = callEffect;
|
|
34
40
|
this.cache = defaultShouldCache;
|
|
41
|
+
if (chainId === undefined) {
|
|
42
|
+
// Cross-chain: reading context.chain throws, via the shared getter.
|
|
43
|
+
Object.defineProperty(this, "_effectName", { value: effectName });
|
|
44
|
+
Object.defineProperty(this, "chain", crossChainChainDescriptor);
|
|
45
|
+
} else {
|
|
46
|
+
this.chain = { id: chainId };
|
|
47
|
+
}
|
|
35
48
|
};
|
|
36
49
|
EffectContext.prototype = effectContextPrototype;
|
|
37
50
|
`)
|
|
@@ -39,35 +52,61 @@ EffectContext.prototype = effectContextPrototype;
|
|
|
39
52
|
@new
|
|
40
53
|
external makeEffectContext: (
|
|
41
54
|
contextParams,
|
|
55
|
+
~chainId: option<int>,
|
|
56
|
+
~effectName: string,
|
|
42
57
|
~defaultShouldCache: bool,
|
|
43
58
|
~callEffect: (Internal.effect, Internal.effectInput) => promise<Internal.effectOutput>,
|
|
44
59
|
) => Internal.effectContext = "EffectContext"
|
|
45
60
|
|
|
46
61
|
let initEffect = (params: contextParams) => {
|
|
47
|
-
let
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
)
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
62
|
+
let handlerChainId = params.item->Internal.getItemChainId
|
|
63
|
+
// A chain-scoped effect always resolves against the chain of the handler that
|
|
64
|
+
// triggered the call, even several effects deep, so the chain id is captured
|
|
65
|
+
// once from the item and reused for the whole nested-call tree.
|
|
66
|
+
let rec makeCaller = (~caller: option<Internal.effect>) => {
|
|
67
|
+
(effect: Internal.effect, input: Internal.effectInput) => {
|
|
68
|
+
let scope = effect.crossChain ? Internal.CrossChain : Internal.Chain(handlerChainId)
|
|
69
|
+
|
|
70
|
+
switch caller {
|
|
71
|
+
| Some(callerEffect) if callerEffect.crossChain && !effect.crossChain =>
|
|
72
|
+
// A cross-chain effect isn't tied to a single chain, so it has no chain
|
|
73
|
+
// to resolve a chain-scoped child against. Reject before any cache work.
|
|
74
|
+
JsError.throwWithMessage(
|
|
75
|
+
`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\`).`,
|
|
76
|
+
)
|
|
77
|
+
| _ => ()
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
let effectContext = makeEffectContext(
|
|
81
|
+
params,
|
|
82
|
+
~chainId=switch scope {
|
|
83
|
+
| Internal.Chain(chainId) => Some(chainId)
|
|
84
|
+
| Internal.CrossChain => None
|
|
85
|
+
},
|
|
86
|
+
~effectName=effect.name,
|
|
87
|
+
~defaultShouldCache=effect.defaultShouldCache,
|
|
88
|
+
~callEffect=makeCaller(~caller=Some(effect)),
|
|
89
|
+
)
|
|
90
|
+
let effectArgs: Internal.effectArgs = {
|
|
91
|
+
input,
|
|
92
|
+
context: effectContext,
|
|
93
|
+
cacheKey: input->S.reverseConvertOrThrow(effect.input)->Utils.Hash.makeOrThrow,
|
|
94
|
+
checkpointId: params.checkpointId,
|
|
95
|
+
}
|
|
96
|
+
LoadLayer.loadEffect(
|
|
97
|
+
~loadManager=params.loadManager,
|
|
98
|
+
~persistence=params.persistence,
|
|
99
|
+
~effect,
|
|
100
|
+
~effectArgs,
|
|
101
|
+
~scope,
|
|
102
|
+
~indexerState=params.indexerState,
|
|
103
|
+
~shouldGroup=params.isPreload,
|
|
104
|
+
~item=params.item,
|
|
105
|
+
~ecosystem=params.config.ecosystem,
|
|
106
|
+
)
|
|
58
107
|
}
|
|
59
|
-
LoadLayer.loadEffect(
|
|
60
|
-
~loadManager=params.loadManager,
|
|
61
|
-
~persistence=params.persistence,
|
|
62
|
-
~effect,
|
|
63
|
-
~effectArgs,
|
|
64
|
-
~indexerState=params.indexerState,
|
|
65
|
-
~shouldGroup=params.isPreload,
|
|
66
|
-
~item=params.item,
|
|
67
|
-
~ecosystem=params.config.ecosystem,
|
|
68
|
-
)
|
|
69
108
|
}
|
|
70
|
-
|
|
109
|
+
makeCaller(~caller=None)
|
|
71
110
|
}
|
|
72
111
|
|
|
73
112
|
type entityContextParams = {
|
package/src/UserContext.res.mjs
CHANGED
|
@@ -25,17 +25,37 @@ Object.defineProperty(effectContextPrototype, "log", {
|
|
|
25
25
|
}
|
|
26
26
|
});
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
// Top-level so the getter is created once, not per context. \`this\` is the
|
|
29
|
+
// EffectContext instance; only cross-chain contexts install it.
|
|
30
|
+
function throwCrossChainChainAccess() {
|
|
31
|
+
throw new Error('context.chain is not available on the cross-chain effect "' + this._effectName + '". Set \`crossChain: false\` in its options to scope the effect to a single chain, then read context.chain.id.');
|
|
32
|
+
}
|
|
33
|
+
var crossChainChainDescriptor = { get: throwCrossChainChainAccess, enumerable: true };
|
|
34
|
+
var EffectContext = function(params, chainId, effectName, defaultShouldCache, callEffect) {
|
|
29
35
|
paramsByThis.set(this, params);
|
|
30
36
|
this.effect = callEffect;
|
|
31
37
|
this.cache = defaultShouldCache;
|
|
38
|
+
if (chainId === undefined) {
|
|
39
|
+
// Cross-chain: reading context.chain throws, via the shared getter.
|
|
40
|
+
Object.defineProperty(this, "_effectName", { value: effectName });
|
|
41
|
+
Object.defineProperty(this, "chain", crossChainChainDescriptor);
|
|
42
|
+
} else {
|
|
43
|
+
this.chain = { id: chainId };
|
|
44
|
+
}
|
|
32
45
|
};
|
|
33
46
|
EffectContext.prototype = effectContextPrototype;
|
|
34
47
|
;
|
|
35
48
|
|
|
36
49
|
function initEffect(params) {
|
|
37
|
-
let
|
|
38
|
-
|
|
50
|
+
let handlerChainId = Internal.getItemChainId(params.item);
|
|
51
|
+
let makeCaller = caller => ((effect, input) => {
|
|
52
|
+
let scope = effect.crossChain ? "crossChain" : handlerChainId;
|
|
53
|
+
if (caller !== undefined && caller.crossChain && !effect.crossChain) {
|
|
54
|
+
Stdlib_JsError.throwWithMessage(`The cross-chain effect "` + caller.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 "` + caller.name + `" chain-scoped (\`crossChain: false\`).`);
|
|
55
|
+
}
|
|
56
|
+
let tmp;
|
|
57
|
+
tmp = scope === "crossChain" ? undefined : scope;
|
|
58
|
+
let effectContext = new EffectContext(params, tmp, effect.name, effect.defaultShouldCache, makeCaller(effect));
|
|
39
59
|
let effectArgs_cacheKey = Utils.Hash.makeOrThrow(S$RescriptSchema.reverseConvertOrThrow(input, effect.input));
|
|
40
60
|
let effectArgs_checkpointId = params.checkpointId;
|
|
41
61
|
let effectArgs = {
|
|
@@ -44,9 +64,9 @@ function initEffect(params) {
|
|
|
44
64
|
cacheKey: effectArgs_cacheKey,
|
|
45
65
|
checkpointId: effectArgs_checkpointId
|
|
46
66
|
};
|
|
47
|
-
return LoadLayer.loadEffect(params.loadManager, params.persistence, effect, effectArgs, params.indexerState, params.isPreload, params.item, params.config.ecosystem);
|
|
48
|
-
};
|
|
49
|
-
return
|
|
67
|
+
return LoadLayer.loadEffect(params.loadManager, params.persistence, effect, effectArgs, scope, params.indexerState, params.isPreload, params.item, params.config.ecosystem);
|
|
68
|
+
});
|
|
69
|
+
return makeCaller(undefined);
|
|
50
70
|
}
|
|
51
71
|
|
|
52
72
|
function getWhereHandler(params, filter) {
|
package/src/Writing.res
CHANGED
|
@@ -16,8 +16,8 @@ let getChangesCount = (state: IndexerState.t) => {
|
|
|
16
16
|
total := total.contents +. (state->InMemoryStore.getInMemTable(~entityConfig)).changesCount
|
|
17
17
|
})
|
|
18
18
|
state
|
|
19
|
-
->IndexerState.
|
|
20
|
-
->
|
|
19
|
+
->IndexerState.effectState
|
|
20
|
+
->EffectState.forEach(inMemTable => {
|
|
21
21
|
total := total.contents +. inMemTable.changesCount
|
|
22
22
|
})
|
|
23
23
|
state
|
|
@@ -38,9 +38,9 @@ let waitForCommit = (state: IndexerState.t): promise<unit> =>
|
|
|
38
38
|
let snapshotEffects = (state: IndexerState.t, ~cache): array<Persistence.updatedEffectCache> => {
|
|
39
39
|
let acc = []
|
|
40
40
|
state
|
|
41
|
-
->IndexerState.
|
|
42
|
-
->
|
|
43
|
-
let {idsToStore, dict, effect, invalidationsCount} = inMemTable
|
|
41
|
+
->IndexerState.effectState
|
|
42
|
+
->EffectState.forEach(inMemTable => {
|
|
43
|
+
let {idsToStore, dict, effect, invalidationsCount, scope, table} = inMemTable
|
|
44
44
|
switch idsToStore {
|
|
45
45
|
| [] => ()
|
|
46
46
|
| ids =>
|
|
@@ -51,17 +51,33 @@ let snapshotEffects = (state: IndexerState.t, ~cache): array<Persistence.updated
|
|
|
51
51
|
}
|
|
52
52
|
)
|
|
53
53
|
let effectName = effect.name
|
|
54
|
-
let
|
|
54
|
+
let tableName = table.tableName
|
|
55
|
+
let effectCacheRecord = switch cache->Utils.Dict.dangerouslyGetNonOption(tableName) {
|
|
55
56
|
| Some(c) => c
|
|
56
57
|
| None =>
|
|
57
|
-
let c: Persistence.effectCacheRecord = {effectName, count: 0}
|
|
58
|
-
cache->Dict.set(
|
|
58
|
+
let c: Persistence.effectCacheRecord = {effectName, scope, tableName, count: 0}
|
|
59
|
+
cache->Dict.set(tableName, c)
|
|
59
60
|
c
|
|
60
61
|
}
|
|
61
62
|
let shouldInitialize = effectCacheRecord.count === 0
|
|
62
63
|
effectCacheRecord.count = effectCacheRecord.count + items->Array.length - invalidationsCount
|
|
63
|
-
Prometheus.EffectCacheCount.set(
|
|
64
|
-
|
|
64
|
+
Prometheus.EffectCacheCount.set(
|
|
65
|
+
~count=effectCacheRecord.count,
|
|
66
|
+
~effectName,
|
|
67
|
+
~scope=scope->Internal.EffectCache.scopeToString,
|
|
68
|
+
)
|
|
69
|
+
acc
|
|
70
|
+
->Array.push(
|
|
71
|
+
(
|
|
72
|
+
{
|
|
73
|
+
table,
|
|
74
|
+
itemSchema: effect.storageMeta.itemSchema,
|
|
75
|
+
items,
|
|
76
|
+
shouldInitialize,
|
|
77
|
+
}: Persistence.updatedEffectCache
|
|
78
|
+
),
|
|
79
|
+
)
|
|
80
|
+
->ignore
|
|
65
81
|
}
|
|
66
82
|
inMemTable.idsToStore = []
|
|
67
83
|
inMemTable.invalidationsCount = 0
|
|
@@ -192,8 +208,8 @@ let dropCommitted = (state: IndexerState.t, ~keepLoadedFromDb) => {
|
|
|
192
208
|
->InMemoryTable.Entity.dropCommittedChanges(~committedCheckpointId, ~keepLoadedFromDb)
|
|
193
209
|
)
|
|
194
210
|
state
|
|
195
|
-
->IndexerState.
|
|
196
|
-
->
|
|
211
|
+
->IndexerState.effectState
|
|
212
|
+
->EffectState.forEach(inMemTable =>
|
|
197
213
|
inMemTable->InMemoryStore.dropCommittedEffects(~committedCheckpointId, ~keepLoadedFromDb)
|
|
198
214
|
)
|
|
199
215
|
}
|
package/src/Writing.res.mjs
CHANGED
|
@@ -2,8 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
import * as Env from "./Env.res.mjs";
|
|
4
4
|
import * as Utils from "./Utils.res.mjs";
|
|
5
|
+
import * as Internal from "./Internal.res.mjs";
|
|
5
6
|
import * as Throttler from "./Throttler.res.mjs";
|
|
6
7
|
import * as Prometheus from "./Prometheus.res.mjs";
|
|
8
|
+
import * as EffectState from "./EffectState.res.mjs";
|
|
7
9
|
import * as IndexerState from "./IndexerState.res.mjs";
|
|
8
10
|
import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
|
|
9
11
|
import * as InMemoryStore from "./InMemoryStore.res.mjs";
|
|
@@ -20,7 +22,7 @@ function getChangesCount(state) {
|
|
|
20
22
|
IndexerState.allEntities(state).forEach(entityConfig => {
|
|
21
23
|
total.contents = total.contents + InMemoryStore.getInMemTable(state, entityConfig).changesCount;
|
|
22
24
|
});
|
|
23
|
-
|
|
25
|
+
EffectState.forEach(IndexerState.effectState(state), inMemTable => {
|
|
24
26
|
total.contents = total.contents + inMemTable.changesCount;
|
|
25
27
|
});
|
|
26
28
|
IndexerState.processedBatches(state).forEach(batch => {
|
|
@@ -35,10 +37,12 @@ function waitForCommit(state) {
|
|
|
35
37
|
|
|
36
38
|
function snapshotEffects(state, cache) {
|
|
37
39
|
let acc = [];
|
|
38
|
-
|
|
40
|
+
EffectState.forEach(IndexerState.effectState(state), inMemTable => {
|
|
39
41
|
let idsToStore = inMemTable.idsToStore;
|
|
40
42
|
let invalidationsCount = inMemTable.invalidationsCount;
|
|
41
43
|
let dict = inMemTable.dict;
|
|
44
|
+
let table = inMemTable.table;
|
|
45
|
+
let scope = inMemTable.scope;
|
|
42
46
|
let effect = inMemTable.effect;
|
|
43
47
|
if (idsToStore.length !== 0) {
|
|
44
48
|
let items = Stdlib_Array.filterMap(idsToStore, id => {
|
|
@@ -51,23 +55,27 @@ function snapshotEffects(state, cache) {
|
|
|
51
55
|
}
|
|
52
56
|
});
|
|
53
57
|
let effectName = effect.name;
|
|
54
|
-
let
|
|
58
|
+
let tableName = table.tableName;
|
|
59
|
+
let c = cache[tableName];
|
|
55
60
|
let effectCacheRecord;
|
|
56
61
|
if (c !== undefined) {
|
|
57
62
|
effectCacheRecord = c;
|
|
58
63
|
} else {
|
|
59
64
|
let c$1 = {
|
|
60
65
|
effectName: effectName,
|
|
66
|
+
scope: scope,
|
|
67
|
+
tableName: tableName,
|
|
61
68
|
count: 0
|
|
62
69
|
};
|
|
63
|
-
cache[
|
|
70
|
+
cache[tableName] = c$1;
|
|
64
71
|
effectCacheRecord = c$1;
|
|
65
72
|
}
|
|
66
73
|
let shouldInitialize = effectCacheRecord.count === 0;
|
|
67
74
|
effectCacheRecord.count = (effectCacheRecord.count + items.length | 0) - invalidationsCount | 0;
|
|
68
|
-
Prometheus.EffectCacheCount.set(effectCacheRecord.count, effectName);
|
|
75
|
+
Prometheus.EffectCacheCount.set(effectCacheRecord.count, effectName, Internal.EffectCache.scopeToString(scope));
|
|
69
76
|
acc.push({
|
|
70
|
-
|
|
77
|
+
table: table,
|
|
78
|
+
itemSchema: effect.storageMeta.itemSchema,
|
|
71
79
|
items: items,
|
|
72
80
|
shouldInitialize: shouldInitialize
|
|
73
81
|
});
|
|
@@ -164,7 +172,7 @@ function commitBatch(state, batch) {
|
|
|
164
172
|
function dropCommitted(state, keepLoadedFromDb) {
|
|
165
173
|
let committedCheckpointId = IndexerState.committedCheckpointId(state);
|
|
166
174
|
IndexerState.allEntities(state).forEach(entityConfig => InMemoryTable.Entity.dropCommittedChanges(InMemoryStore.getInMemTable(state, entityConfig), committedCheckpointId, keepLoadedFromDb));
|
|
167
|
-
|
|
175
|
+
EffectState.forEach(IndexerState.effectState(state), inMemTable => InMemoryStore.dropCommittedEffects(inMemTable, committedCheckpointId, keepLoadedFromDb));
|
|
168
176
|
}
|
|
169
177
|
|
|
170
178
|
async function awaitCapacity(state) {
|
package/src/bindings/NodeJs.res
CHANGED
|
@@ -173,5 +173,10 @@ module Fs = {
|
|
|
173
173
|
|
|
174
174
|
@module("fs") @scope("promises")
|
|
175
175
|
external readdir: Path.t => promise<array<string>> = "readdir"
|
|
176
|
+
|
|
177
|
+
type stats
|
|
178
|
+
@module("fs") @scope("promises")
|
|
179
|
+
external stat: Path.t => promise<stats> = "stat"
|
|
180
|
+
@send external statsIsDirectory: stats => bool = "isDirectory"
|
|
176
181
|
}
|
|
177
182
|
}
|
|
@@ -488,6 +488,16 @@ let make = (
|
|
|
488
488
|
|
|
489
489
|
switch maybeConfig {
|
|
490
490
|
| None => ()
|
|
491
|
+
// Exclude instructions from failed transactions. HyperSync has no
|
|
492
|
+
// server-side predicate to filter instructions by parent-transaction
|
|
493
|
+
// success (`InstructionSelection` only exposes `is_inner`, and
|
|
494
|
+
// instruction/transaction selections union at block level rather than
|
|
495
|
+
// joining), so we filter client-side on the `isCommitted` flag HyperSync
|
|
496
|
+
// already delivers on every instruction row (a required column, zero extra
|
|
497
|
+
// bandwidth). When HyperSync adds a server-side `is_committed` predicate the
|
|
498
|
+
// query can push this down and the client-side check becomes a redundant
|
|
499
|
+
// safety net.
|
|
500
|
+
| Some(_) if !instr.isCommitted => ()
|
|
491
501
|
| Some(onEventRegistration) =>
|
|
492
502
|
let eventConfig =
|
|
493
503
|
onEventRegistration.eventConfig->(
|
|
@@ -415,7 +415,7 @@ function make(param) {
|
|
|
415
415
|
let byteLengths = Stdlib_Option.getOr(orderingByProgram[instr.programId], []);
|
|
416
416
|
let contractAddress = instr.programId;
|
|
417
417
|
let maybeConfig = probeRouter(eventRouter, programId, instr, byteLengths, contractAddress, contractNameByAddress);
|
|
418
|
-
if (maybeConfig !== undefined) {
|
|
418
|
+
if (maybeConfig !== undefined && instr.isCommitted) {
|
|
419
419
|
let eventConfig = maybeConfig.eventConfig;
|
|
420
420
|
let logKey = instr.slot.toString() + ":" + instr.transactionIndex.toString() + ":" + serializeInstructionAddress(instr.instructionAddress);
|
|
421
421
|
let maybeLogs = Stdlib_Option.map(logsByKey[logKey], logs => logs.map(log => ({
|