envio 3.13.0 → 3.14.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/bin.mjs +10 -1
- package/index.d.ts +11 -0
- package/package.json +8 -20
- package/rescript.json +1 -4
- package/src/Config.res +78 -100
- package/src/Config.res.mjs +55 -64
- package/src/Core.res +67 -6
- package/src/Core.res.mjs +58 -4
- package/src/EventConfigBuilder.res +4 -10
- package/src/EventConfigBuilder.res.mjs +1 -3
- package/src/EventProcessing.res +6 -6
- package/src/EventProcessing.res.mjs +8 -6
- package/src/ExitOnCaughtUp.res +5 -1
- package/src/ExitOnCaughtUp.res.mjs +8 -3
- package/src/HandlerLoader.res +98 -36
- package/src/HandlerLoader.res.mjs +89 -24
- package/src/HandlerTypeCheck.mjs +14 -0
- package/src/HandlerTypeCheckWorker.mjs +68 -0
- package/src/Internal.res +1 -1
- package/src/LoadLayer.res +39 -7
- package/src/LoadLayer.res.mjs +65 -38
- package/src/LoadLayer.resi +28 -0
- package/src/Main.res +17 -12
- package/src/Main.res.mjs +9 -3
- package/src/NodeVersion.mjs +13 -0
- package/src/Persistence.res +79 -38
- package/src/Persistence.res.mjs +41 -10
- package/src/PgStorage.res +86 -39
- package/src/PgStorage.res.mjs +83 -32
- package/src/ResumePlan.res +172 -0
- package/src/ResumePlan.res.mjs +122 -0
- package/src/Supervisor.res +8 -8
- package/src/Supervisor.res.mjs +6 -1
- package/src/SyncWrites.res +86 -0
- package/src/SyncWrites.res.mjs +85 -0
- package/src/TestIndexer.res +14 -4
- package/src/TestIndexer.res.mjs +3 -2
- package/src/TsModuleHooks.mjs +121 -0
- package/src/UserContext.res +377 -62
- package/src/UserContext.res.mjs +297 -39
- package/src/Utils.res +0 -8
- package/src/Worker.res +8 -0
- package/src/Worker.res.mjs +11 -0
- package/src/bindings/NodeJs.res +8 -7
- package/src/bindings/NodeJs.res.mjs +0 -3
- package/src/bindings/Pino.res +2 -2
- package/src/db/InternalTable.res +52 -2
- package/src/db/InternalTable.res.mjs +32 -3
- package/src/db/Table.res +10 -0
- package/src/db/Table.res.mjs +22 -11
- package/src/sources/FuelHyperSync.res +2 -0
- package/src/sources/FuelHyperSync.res.mjs +1 -0
- package/src/sources/FuelHyperSync.resi +1 -0
- package/src/sources/FuelHyperSyncClient.res +25 -8
- package/src/sources/FuelHyperSyncClient.res.mjs +8 -2
- package/src/sources/FuelHyperSyncSource.res +24 -22
- package/src/sources/FuelHyperSyncSource.res.mjs +18 -13
- package/src/subgraph/abi-types.ts +63 -0
- package/src/subgraph/assemblyscript.ts +85 -0
- package/src/subgraph/blocks.ts +183 -0
- package/src/subgraph/calls.ts +149 -0
- package/src/subgraph/conformance.ts +83 -0
- package/src/subgraph/errors.ts +96 -0
- package/src/subgraph/graph-cli-worker.mjs +26 -0
- package/src/subgraph/graph-cli.ts +190 -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 +2285 -0
- package/src/subgraph/hosts.ts +158 -0
- package/src/subgraph/rpc.ts +65 -0
- package/src/subgraph/runtime.ts +595 -0
- package/src/subgraph/scope.ts +68 -0
- package/src/tui/InitApi.res +85 -0
- package/src/tui/{components/CustomHooks.res.mjs → InitApi.res.mjs} +11 -64
- package/src/tui/Tui.res +142 -249
- package/src/tui/Tui.res.mjs +123 -332
- package/src/Js.shim.ts +0 -11
- package/src/bindings/DateFns.res +0 -74
- package/src/bindings/DateFns.res.mjs +0 -22
- package/src/sources/FuelSDK.res +0 -38
- package/src/sources/FuelSDK.res.mjs +0 -27
- package/src/sources/vendored-fuel-abi-coder.js +0 -1792
- package/src/tui/bindings/Ink.res +0 -369
- package/src/tui/bindings/Ink.res.mjs +0 -66
- package/src/tui/bindings/Style.res +0 -123
- package/src/tui/bindings/Style.res.mjs +0 -2
- package/src/tui/components/BufferedProgressBar.res +0 -40
- package/src/tui/components/BufferedProgressBar.res.mjs +0 -57
- package/src/tui/components/CustomHooks.res +0 -122
- package/src/tui/components/Messages.res +0 -40
- package/src/tui/components/Messages.res.mjs +0 -71
- package/src/tui/components/SyncETA.res +0 -179
- package/src/tui/components/SyncETA.res.mjs +0 -249
- package/src/tui/components/TuiData.res +0 -98
- package/src/tui/components/TuiData.res.mjs +0 -73
package/src/UserContext.res.mjs
CHANGED
|
@@ -1,19 +1,83 @@
|
|
|
1
1
|
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
2
|
|
|
3
3
|
import * as Utils from "./Utils.res.mjs";
|
|
4
|
+
import * as ChainId from "./ChainId.res.mjs";
|
|
4
5
|
import * as Logging from "./Logging.res.mjs";
|
|
5
6
|
import * as Internal from "./Internal.res.mjs";
|
|
6
7
|
import * as Ecosystem from "./Ecosystem.res.mjs";
|
|
7
8
|
import * as LoadLayer from "./LoadLayer.res.mjs";
|
|
9
|
+
import * as SyncWrites from "./SyncWrites.res.mjs";
|
|
8
10
|
import * as EntityFilter from "./db/EntityFilter.res.mjs";
|
|
9
|
-
import * as IndexerState from "./IndexerState.res.mjs";
|
|
10
11
|
import * as ErrorHandling from "./ErrorHandling.res.mjs";
|
|
11
12
|
import * as InMemoryStore from "./InMemoryStore.res.mjs";
|
|
12
13
|
import * as InMemoryTable from "./InMemoryTable.res.mjs";
|
|
13
14
|
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
14
15
|
import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
|
|
16
|
+
import * as Stdlib_Promise from "@rescript/runtime/lib/es6/Stdlib_Promise.js";
|
|
15
17
|
import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
|
|
16
18
|
import * as S$RescriptSchema from "rescript-schema/src/S.res.mjs";
|
|
19
|
+
import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
|
|
20
|
+
|
|
21
|
+
let Suspend = /* @__PURE__ */Primitive_exceptions.create("UserContext.Suspend");
|
|
22
|
+
|
|
23
|
+
function isSuspend(exn) {
|
|
24
|
+
return exn.RE_EXN_ID === Suspend;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function makeSyncState() {
|
|
28
|
+
return {
|
|
29
|
+
status: "Active",
|
|
30
|
+
pending: undefined,
|
|
31
|
+
memo: undefined,
|
|
32
|
+
writes: undefined
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function getPending(sync) {
|
|
37
|
+
let pending = sync.pending;
|
|
38
|
+
if (pending !== undefined) {
|
|
39
|
+
return pending;
|
|
40
|
+
}
|
|
41
|
+
let pending$1 = [];
|
|
42
|
+
sync.pending = pending$1;
|
|
43
|
+
return pending$1;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function getMemo(sync) {
|
|
47
|
+
let memo = sync.memo;
|
|
48
|
+
if (memo !== undefined) {
|
|
49
|
+
return memo;
|
|
50
|
+
}
|
|
51
|
+
let memo$1 = {};
|
|
52
|
+
sync.memo = memo$1;
|
|
53
|
+
return memo$1;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function checkStatusOrThrow(params, access) {
|
|
57
|
+
let exn = params.sync.status;
|
|
58
|
+
if (typeof exn !== "object") {
|
|
59
|
+
if (exn === "Active") {
|
|
60
|
+
return;
|
|
61
|
+
} else {
|
|
62
|
+
return ErrorHandling.mkLogAndRaise(Ecosystem.getItemLogger(params.item, params.config.ecosystem), undefined, new Error(`Impossible to access ` + access + ` after the handler is resolved. Make sure you didn't miss an await in the handler.`));
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
throw exn._0;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function scheduleAndSuspend(params, promise) {
|
|
69
|
+
getPending(params.sync).push(promise);
|
|
70
|
+
params.sync.status = {
|
|
71
|
+
TAG: "Aborted",
|
|
72
|
+
_0: {
|
|
73
|
+
RE_EXN_ID: Suspend
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
throw {
|
|
77
|
+
RE_EXN_ID: Suspend,
|
|
78
|
+
Error: new Error()
|
|
79
|
+
};
|
|
80
|
+
}
|
|
17
81
|
|
|
18
82
|
let paramsByThis = new WeakMap();
|
|
19
83
|
|
|
@@ -47,29 +111,71 @@ var EffectContext = function(params, chainId, effectName, defaultShouldCache, ca
|
|
|
47
111
|
EffectContext.prototype = effectContextPrototype;
|
|
48
112
|
;
|
|
49
113
|
|
|
50
|
-
function
|
|
51
|
-
let handlerChainId = Internal.getItemChainId(params.item);
|
|
114
|
+
function prepareEffectCall(params, effect, input, caller) {
|
|
52
115
|
let isCrossChain = effect => Stdlib_Option.getOr(effect.crossChain, params.config.defaultCrossChain);
|
|
53
|
-
let
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
116
|
+
let scope = isCrossChain(effect) ? "crossChain" : Internal.getItemChainId(params.item);
|
|
117
|
+
if (caller !== undefined && isCrossChain(caller) && !isCrossChain(effect)) {
|
|
118
|
+
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\`).`);
|
|
119
|
+
}
|
|
120
|
+
let tmp;
|
|
121
|
+
tmp = scope === "crossChain" ? undefined : Primitive_option.some(scope);
|
|
122
|
+
let effectContext = new EffectContext(params, tmp, effect.name, effect.defaultShouldCache, (nested, nestedInput) => callEffectAsync(params, nested, nestedInput, effect));
|
|
123
|
+
let effectArgs_cacheKey = Utils.Hash.makeOrThrow(S$RescriptSchema.reverseConvertOrThrow(input, effect.input));
|
|
124
|
+
let effectArgs_chainId = Internal.getItemChainId(params.item);
|
|
125
|
+
let effectArgs_checkpointId = params.checkpointId;
|
|
126
|
+
let effectArgs = {
|
|
127
|
+
input: input,
|
|
128
|
+
context: effectContext,
|
|
129
|
+
cacheKey: effectArgs_cacheKey,
|
|
130
|
+
chainId: effectArgs_chainId,
|
|
131
|
+
checkpointId: effectArgs_checkpointId
|
|
132
|
+
};
|
|
133
|
+
return [
|
|
134
|
+
scope,
|
|
135
|
+
effectArgs
|
|
136
|
+
];
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function callEffectAsync(params, effect, input, caller) {
|
|
140
|
+
let match = prepareEffectCall(params, effect, input, caller);
|
|
141
|
+
return LoadLayer.loadEffect(params.loadManager, params.persistence, effect, match[1], match[0], params.indexerState, params.isPreload, params.item, params.config.ecosystem);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function initEffect(params) {
|
|
145
|
+
return (effect, input) => {
|
|
146
|
+
checkStatusOrThrow(params, "context.effect");
|
|
147
|
+
return callEffectAsync(params, effect, input, undefined);
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function effectMemoKey(effect, scope, cacheKey) {
|
|
152
|
+
if (scope === "crossChain") {
|
|
153
|
+
return effect.name + `.` + cacheKey;
|
|
154
|
+
} else {
|
|
155
|
+
return effect.name + `.` + ChainId.toString(scope) + `.` + cacheKey;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function initEffectSync(params) {
|
|
160
|
+
return (effect, input) => {
|
|
161
|
+
checkStatusOrThrow(params, "context.effectSync");
|
|
162
|
+
let match = prepareEffectCall(params, effect, input, undefined);
|
|
163
|
+
let effectArgs = match[1];
|
|
164
|
+
let scope = match[0];
|
|
165
|
+
let memo = getMemo(params.sync);
|
|
166
|
+
let memoKey = effectMemoKey(effect, scope, effectArgs.cacheKey);
|
|
167
|
+
let output = memo[memoKey];
|
|
168
|
+
if (output !== undefined) {
|
|
169
|
+
return Primitive_option.valFromOption(output);
|
|
57
170
|
}
|
|
58
|
-
let
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
let
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
cacheKey: effectArgs_cacheKey,
|
|
67
|
-
chainId: handlerChainId,
|
|
68
|
-
checkpointId: effectArgs_checkpointId
|
|
69
|
-
};
|
|
70
|
-
return LoadLayer.loadEffect(params.loadManager, params.persistence, effect, effectArgs, scope, params.indexerState, params.isPreload, params.item, params.config.ecosystem);
|
|
71
|
-
});
|
|
72
|
-
return makeCaller(undefined);
|
|
171
|
+
let inMemTable = InMemoryStore.getEffectInMemTable(params.indexerState, effect, scope);
|
|
172
|
+
if (!InMemoryStore.hasEffectOutput(inMemTable, effectArgs.cacheKey)) {
|
|
173
|
+
return scheduleAndSuspend(params, LoadLayer.loadEffect(params.loadManager, params.persistence, effect, effectArgs, scope, params.indexerState, params.isPreload, params.item, params.config.ecosystem));
|
|
174
|
+
}
|
|
175
|
+
let output$1 = InMemoryStore.getEffectOutputUnsafe(inMemTable, effectArgs.cacheKey);
|
|
176
|
+
memo[memoKey] = output$1;
|
|
177
|
+
return output$1;
|
|
178
|
+
};
|
|
73
179
|
}
|
|
74
180
|
|
|
75
181
|
function entityScope(params) {
|
|
@@ -92,26 +198,88 @@ function throwClickHouseReadOnly(entityConfig, op) {
|
|
|
92
198
|
return Stdlib_JsError.throwWithMessage(`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.`);
|
|
93
199
|
}
|
|
94
200
|
|
|
201
|
+
function writeChange(params, change) {
|
|
202
|
+
let scope = entityScope(params);
|
|
203
|
+
let writes = params.sync.writes;
|
|
204
|
+
if (writes !== undefined) {
|
|
205
|
+
return SyncWrites.record(writes, params.entityConfig, scope, change);
|
|
206
|
+
} else {
|
|
207
|
+
return SyncWrites.write(params.indexerState, params.entityConfig, scope, change);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function getRoundWrite(params, entityId) {
|
|
212
|
+
let writes = params.sync.writes;
|
|
213
|
+
if (writes !== undefined) {
|
|
214
|
+
return SyncWrites.getById(writes, params.entityConfig, entityId);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function getSyncHandler(params, entityId) {
|
|
219
|
+
let entity = getRoundWrite(params, entityId);
|
|
220
|
+
if (entity !== undefined) {
|
|
221
|
+
return Primitive_option.valFromOption(entity);
|
|
222
|
+
}
|
|
223
|
+
let entity$1 = LoadLayer.getByIdInMemory(params.entityConfig, entityScope(params), params.indexerState, entityId);
|
|
224
|
+
if (entity$1 !== undefined) {
|
|
225
|
+
return Primitive_option.valFromOption(entity$1);
|
|
226
|
+
} else {
|
|
227
|
+
return scheduleAndSuspend(params, LoadLayer.loadById(params.loadManager, params.persistence, params.entityConfig, entityScope(params), params.indexerState, params.isPreload, params.item, params.config.ecosystem, entityId));
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function getWhereSyncHandler(params, filter) {
|
|
232
|
+
let entityConfig = params.entityConfig;
|
|
233
|
+
let filter$1 = EntityFilter.parseOrThrow(filter, entityConfig.name, entityConfig.table);
|
|
234
|
+
let entities = LoadLayer.getByFilterInMemory(entityConfig, entityScope(params), params.indexerState, filter$1);
|
|
235
|
+
if (entities === undefined) {
|
|
236
|
+
return scheduleAndSuspend(params, LoadLayer.loadByParsedFilter(params.loadManager, params.persistence, entityConfig, entityScope(params), params.indexerState, params.isPreload, params.item, params.config.ecosystem, filter$1));
|
|
237
|
+
}
|
|
238
|
+
let writes = params.sync.writes;
|
|
239
|
+
if (writes !== undefined) {
|
|
240
|
+
return SyncWrites.overlayFilter(writes, entities, entityConfig, filter$1);
|
|
241
|
+
} else {
|
|
242
|
+
return entities;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function getInBlockSyncHandler(params, entityId) {
|
|
247
|
+
let entity = getRoundWrite(params, entityId);
|
|
248
|
+
if (entity !== undefined) {
|
|
249
|
+
return Primitive_option.valFromOption(entity);
|
|
250
|
+
}
|
|
251
|
+
let inMemTable = InMemoryStore.getInMemTable(params.indexerState, params.entityConfig, entityScope(params));
|
|
252
|
+
let change = inMemTable.latestEntityChangeById[entityId];
|
|
253
|
+
if (change !== undefined && change.checkpointId === params.checkpointId) {
|
|
254
|
+
return InMemoryTable.Entity.mapChangeToEntity(change);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
95
258
|
let entityTraps_get = (params, prop) => {
|
|
259
|
+
checkStatusOrThrow(params, `context.` + params.entityConfig.name + `.` + prop);
|
|
96
260
|
let isClickHouseOnly = !params.entityConfig.storage.postgres;
|
|
97
|
-
let
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
261
|
+
let set = params.isPreload ? noopSet : entity => {
|
|
262
|
+
checkStatusOrThrow(params, `context.` + params.entityConfig.name + `.set`);
|
|
263
|
+
writeChange(params, {
|
|
264
|
+
type: "SET",
|
|
265
|
+
entityId: entity.id,
|
|
266
|
+
entity: entity,
|
|
267
|
+
checkpointId: params.checkpointId
|
|
268
|
+
});
|
|
269
|
+
};
|
|
105
270
|
switch (prop) {
|
|
106
271
|
case "deleteUnsafe" :
|
|
107
272
|
if (params.isPreload) {
|
|
108
273
|
return noopDeleteUnsafe;
|
|
109
274
|
} else {
|
|
110
|
-
return entityId =>
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
275
|
+
return entityId => {
|
|
276
|
+
checkStatusOrThrow(params, `context.` + params.entityConfig.name + `.deleteUnsafe`);
|
|
277
|
+
writeChange(params, {
|
|
278
|
+
type: "DELETE",
|
|
279
|
+
entityId: entityId,
|
|
280
|
+
checkpointId: params.checkpointId
|
|
281
|
+
});
|
|
282
|
+
};
|
|
115
283
|
}
|
|
116
284
|
case "get" :
|
|
117
285
|
if (isClickHouseOnly) {
|
|
@@ -119,6 +287,8 @@ let entityTraps_get = (params, prop) => {
|
|
|
119
287
|
} else {
|
|
120
288
|
return entityId => LoadLayer.loadById(params.loadManager, params.persistence, params.entityConfig, entityScope(params), params.indexerState, params.isPreload, params.item, params.config.ecosystem, entityId);
|
|
121
289
|
}
|
|
290
|
+
case "getInBlockSync" :
|
|
291
|
+
return entityId => getInBlockSyncHandler(params, entityId);
|
|
122
292
|
case "getOrCreate" :
|
|
123
293
|
if (isClickHouseOnly) {
|
|
124
294
|
return _entity => throwClickHouseReadOnly(params.entityConfig, "getOrCreate");
|
|
@@ -144,12 +314,16 @@ let entityTraps_get = (params, prop) => {
|
|
|
144
314
|
}
|
|
145
315
|
});
|
|
146
316
|
}
|
|
317
|
+
case "getSync" :
|
|
318
|
+
return entityId => getSyncHandler(params, entityId);
|
|
147
319
|
case "getWhere" :
|
|
148
320
|
if (isClickHouseOnly) {
|
|
149
321
|
return _filter => throwClickHouseReadOnly(params.entityConfig, "getWhere");
|
|
150
322
|
} else {
|
|
151
323
|
return filter => getWhereHandler(params, filter);
|
|
152
324
|
}
|
|
325
|
+
case "getWhereSync" :
|
|
326
|
+
return filter => getWhereSyncHandler(params, filter);
|
|
153
327
|
case "set" :
|
|
154
328
|
return set;
|
|
155
329
|
default:
|
|
@@ -161,16 +335,78 @@ let entityTraps = {
|
|
|
161
335
|
get: entityTraps_get
|
|
162
336
|
};
|
|
163
337
|
|
|
164
|
-
|
|
165
|
-
if (
|
|
166
|
-
|
|
338
|
+
async function runSyncRound(params, fn, round) {
|
|
339
|
+
if (round > 10000) {
|
|
340
|
+
Stdlib_JsError.throwWithMessage(`The handler suspended on a synchronous read too many times: gave up after ` + (10000).toString() + ` rounds. This usually means the code isn't deterministic across reruns.`);
|
|
341
|
+
}
|
|
342
|
+
params.sync.status = "Active";
|
|
343
|
+
params.sync.pending = undefined;
|
|
344
|
+
let writes = SyncWrites.make();
|
|
345
|
+
params.sync.writes = writes;
|
|
346
|
+
let suspended;
|
|
347
|
+
let exit = 0;
|
|
348
|
+
let val;
|
|
349
|
+
try {
|
|
350
|
+
val = fn();
|
|
351
|
+
exit = 1;
|
|
352
|
+
} catch (raw_exn) {
|
|
353
|
+
let exn = Primitive_exceptions.internalToException(raw_exn);
|
|
354
|
+
params.sync.writes = undefined;
|
|
355
|
+
if (exn.RE_EXN_ID === Suspend) {
|
|
356
|
+
suspended = true;
|
|
357
|
+
} else {
|
|
358
|
+
let pending = params.sync.pending;
|
|
359
|
+
if (pending !== undefined) {
|
|
360
|
+
params.sync.pending = undefined;
|
|
361
|
+
pending.forEach(promise => {
|
|
362
|
+
Utils.$$Promise.silentCatch(promise);
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
throw exn;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
if (exit === 1) {
|
|
369
|
+
params.sync.writes = undefined;
|
|
370
|
+
let match = params.sync.status;
|
|
371
|
+
if (typeof match !== "object") {
|
|
372
|
+
if (match === "Active") {
|
|
373
|
+
SyncWrites.commit(writes, params.indexerState);
|
|
374
|
+
suspended = false;
|
|
375
|
+
} else {
|
|
376
|
+
SyncWrites.commit(writes, params.indexerState);
|
|
377
|
+
suspended = false;
|
|
378
|
+
}
|
|
379
|
+
} else {
|
|
380
|
+
suspended = true;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
if (!suspended) {
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
let pending$1 = Stdlib_Option.getOr(params.sync.pending, []);
|
|
387
|
+
params.sync.pending = undefined;
|
|
388
|
+
let errors = [];
|
|
389
|
+
await Promise.all(pending$1.map(promise => Stdlib_Promise.$$catch(promise, exn => {
|
|
390
|
+
errors.push(exn);
|
|
391
|
+
return Promise.resolve();
|
|
392
|
+
})));
|
|
393
|
+
let exn$1 = errors[0];
|
|
394
|
+
if (exn$1 !== undefined) {
|
|
395
|
+
throw exn$1;
|
|
167
396
|
}
|
|
397
|
+
return await runSyncRound(params, fn, round + 1 | 0);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
let handlerTraps_get = (params, prop) => {
|
|
401
|
+
checkStatusOrThrow(params, `context.` + prop);
|
|
168
402
|
switch (prop) {
|
|
169
403
|
case "chain" :
|
|
170
404
|
let chainId = Internal.getItemChainId(params.item);
|
|
171
405
|
return params.chains[chainId];
|
|
172
406
|
case "effect" :
|
|
173
407
|
return initEffect(params);
|
|
408
|
+
case "effectSync" :
|
|
409
|
+
return initEffectSync(params);
|
|
174
410
|
case "isPreload" :
|
|
175
411
|
return params.isPreload;
|
|
176
412
|
case "log" :
|
|
@@ -179,6 +415,8 @@ let handlerTraps_get = (params, prop) => {
|
|
|
179
415
|
} else {
|
|
180
416
|
return Ecosystem.getItemUserLogger(params.item, params.config.ecosystem);
|
|
181
417
|
}
|
|
418
|
+
case "runSync" :
|
|
419
|
+
return fn => runSyncRound(params, fn, 1);
|
|
182
420
|
default:
|
|
183
421
|
let entityConfig = params.config.userEntitiesByName[prop];
|
|
184
422
|
if (entityConfig !== undefined) {
|
|
@@ -191,7 +429,7 @@ let handlerTraps_get = (params, prop) => {
|
|
|
191
429
|
isPreload: params.isPreload,
|
|
192
430
|
chains: params.chains,
|
|
193
431
|
config: params.config,
|
|
194
|
-
|
|
432
|
+
sync: params.sync,
|
|
195
433
|
entityConfig: entityConfig
|
|
196
434
|
}, entityTraps);
|
|
197
435
|
} else {
|
|
@@ -208,16 +446,36 @@ function getHandlerContext(params) {
|
|
|
208
446
|
return new Proxy(params, handlerTraps);
|
|
209
447
|
}
|
|
210
448
|
|
|
449
|
+
let maxSyncRounds = 10000;
|
|
450
|
+
|
|
211
451
|
export {
|
|
452
|
+
Suspend,
|
|
453
|
+
isSuspend,
|
|
454
|
+
makeSyncState,
|
|
455
|
+
getPending,
|
|
456
|
+
getMemo,
|
|
457
|
+
checkStatusOrThrow,
|
|
458
|
+
scheduleAndSuspend,
|
|
212
459
|
paramsByThis,
|
|
213
460
|
effectContextPrototype,
|
|
461
|
+
prepareEffectCall,
|
|
462
|
+
callEffectAsync,
|
|
214
463
|
initEffect,
|
|
464
|
+
effectMemoKey,
|
|
465
|
+
initEffectSync,
|
|
215
466
|
entityScope,
|
|
216
467
|
getWhereHandler,
|
|
217
468
|
noopSet,
|
|
218
469
|
noopDeleteUnsafe,
|
|
219
470
|
throwClickHouseReadOnly,
|
|
471
|
+
writeChange,
|
|
472
|
+
getRoundWrite,
|
|
473
|
+
getSyncHandler,
|
|
474
|
+
getWhereSyncHandler,
|
|
475
|
+
getInBlockSyncHandler,
|
|
220
476
|
entityTraps,
|
|
477
|
+
maxSyncRounds,
|
|
478
|
+
runSyncRound,
|
|
221
479
|
handlerTraps,
|
|
222
480
|
getHandlerContext,
|
|
223
481
|
}
|
package/src/Utils.res
CHANGED
|
@@ -10,14 +10,6 @@ external toMethod: 'a => 'a = "%unsafe_to_method"
|
|
|
10
10
|
|
|
11
11
|
@val external importPath: string => promise<unknown> = "import"
|
|
12
12
|
|
|
13
|
-
@val
|
|
14
|
-
external importPathWithJson: (
|
|
15
|
-
string,
|
|
16
|
-
@as(json`{with: {type: "json"}}`) _,
|
|
17
|
-
) => promise<{
|
|
18
|
-
"default": JSON.t,
|
|
19
|
-
}> = "import"
|
|
20
|
-
|
|
21
13
|
// Half the delay fixed, half spread across it. Every indexer pointed at one
|
|
22
14
|
// provider loses its stream, or gives up on a quiet chain, in the same instant
|
|
23
15
|
// that provider blinks, and putting them all back on the same schedule is how a
|
package/src/Worker.res
CHANGED
|
@@ -82,6 +82,14 @@ let bindToSupervisor = () => {
|
|
|
82
82
|
|
|
83
83
|
%%private(let send = (message: workerMessage) => NodeJs.Process.sendToParent(message)->ignore)
|
|
84
84
|
|
|
85
|
+
// A worker done with its chains exits only once its final snapshot is on the
|
|
86
|
+
// channel: the last periodic one can be up to an interval old, and the
|
|
87
|
+
// supervisor would go on reporting those chains as it left them.
|
|
88
|
+
let exitAfterReporting = (~metrics: Metrics.t) =>
|
|
89
|
+
NodeJs.Process.sendToParentThen(Snapshot({metrics, runtime: Metrics.sampleRuntime()}), () =>
|
|
90
|
+
NodeJs.process->NodeJs.exitWithCode(Success)
|
|
91
|
+
)->ignore
|
|
92
|
+
|
|
85
93
|
// Reports this process's chains and its own runtime for as long as it runs, so
|
|
86
94
|
// the supervisor can merge every worker's into the one snapshot the run serves,
|
|
87
95
|
// and listens for the one decision the supervisor makes on the run's behalf.
|
package/src/Worker.res.mjs
CHANGED
|
@@ -51,6 +51,16 @@ function bindToSupervisor() {
|
|
|
51
51
|
});
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
function exitAfterReporting(metrics) {
|
|
55
|
+
process.send({
|
|
56
|
+
kind: "snapshot",
|
|
57
|
+
metrics: metrics,
|
|
58
|
+
runtime: Metrics.sampleRuntime()
|
|
59
|
+
}, () => {
|
|
60
|
+
Process.exit(0);
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
54
64
|
function bindRun(getMetrics, onReleaseRealtime) {
|
|
55
65
|
if (isEnabled) {
|
|
56
66
|
Metrics.startRuntimeCollectors();
|
|
@@ -76,6 +86,7 @@ export {
|
|
|
76
86
|
config,
|
|
77
87
|
isEnabled,
|
|
78
88
|
bindToSupervisor,
|
|
89
|
+
exitAfterReporting,
|
|
79
90
|
bindRun,
|
|
80
91
|
}
|
|
81
92
|
/* configSchema Not a pure module */
|
package/src/bindings/NodeJs.res
CHANGED
|
@@ -69,9 +69,17 @@ module Process = {
|
|
|
69
69
|
// `process` rather than off a namespace import, which would drop the
|
|
70
70
|
// receiver Node's own implementations read.
|
|
71
71
|
@val @scope("process") external sendToParent: 'msg => bool = "send"
|
|
72
|
+
// The callback runs once the message is handed to the channel.
|
|
73
|
+
@val @scope("process") external sendToParentThen: ('msg, unit => unit) => bool = "send"
|
|
72
74
|
@val @scope("process")
|
|
73
75
|
external onMessage: (@as("message") _, 'msg => unit) => unit = "on"
|
|
74
76
|
@val @scope("process") external onSignal: (string, unit => unit) => unit = "on"
|
|
77
|
+
@val @scope("process") external offSignal: (string, unit => unit) => unit = "removeListener"
|
|
78
|
+
@val @scope("process") external listenerCount: string => int = "listenerCount"
|
|
79
|
+
@val @scope("process") external pid: int = "pid"
|
|
80
|
+
@val @scope("process") external kill: (int, string) => bool = "kill"
|
|
81
|
+
@val @scope("process")
|
|
82
|
+
external onExit: (@as("exit") _, unit => unit) => unit = "on"
|
|
75
83
|
// Present only in a process forked with an IPC channel.
|
|
76
84
|
@val @scope("process") external channel: Nullable.t<unknown> = "channel"
|
|
77
85
|
@val @scope("process")
|
|
@@ -205,17 +213,10 @@ module Url = {
|
|
|
205
213
|
module ImportMeta = {
|
|
206
214
|
type t = {url: Url.t}
|
|
207
215
|
@val external importMeta: t = "import.meta"
|
|
208
|
-
// Get import.meta.url as string for module registration
|
|
209
|
-
@val external url: string = "import.meta.url"
|
|
210
216
|
// Resolve module specifier to file:// URL
|
|
211
217
|
@val external resolve: string => string = "import.meta.resolve"
|
|
212
218
|
}
|
|
213
219
|
|
|
214
|
-
module Module = {
|
|
215
|
-
// Register ESM loader hooks (e.g., for TypeScript support via tsx)
|
|
216
|
-
@module("node:module") external register: (string, string) => unit = "register"
|
|
217
|
-
}
|
|
218
|
-
|
|
219
220
|
module Path = {
|
|
220
221
|
type t
|
|
221
222
|
|
|
@@ -41,8 +41,6 @@ let Url$1 = {};
|
|
|
41
41
|
|
|
42
42
|
let ImportMeta = {};
|
|
43
43
|
|
|
44
|
-
let Module = {};
|
|
45
|
-
|
|
46
44
|
function getDirname(importMeta) {
|
|
47
45
|
return Path.dirname(Url.fileURLToPath(importMeta.url));
|
|
48
46
|
}
|
|
@@ -68,7 +66,6 @@ export {
|
|
|
68
66
|
ChildProcess,
|
|
69
67
|
Url$1 as Url,
|
|
70
68
|
ImportMeta,
|
|
71
|
-
Module,
|
|
72
69
|
Path$1 as Path,
|
|
73
70
|
WorkerThreads,
|
|
74
71
|
Fs,
|
package/src/bindings/Pino.res
CHANGED
|
@@ -113,8 +113,8 @@ module ECS = {
|
|
|
113
113
|
}
|
|
114
114
|
|
|
115
115
|
/**
|
|
116
|
-
|
|
117
|
-
|
|
116
|
+
Logs through `console.log` rather than `stream.write`, so that the TUI can print
|
|
117
|
+
them above its frame.
|
|
118
118
|
*/
|
|
119
119
|
module MultiStreamLogger = {
|
|
120
120
|
type stream = {write: string => unit}
|
package/src/db/InternalTable.res
CHANGED
|
@@ -159,12 +159,14 @@ WHERE "${table.tableName}"."chain_id" = dead.chain_id
|
|
|
159
159
|
->Utils.Promise.ignoreValue
|
|
160
160
|
}
|
|
161
161
|
|
|
162
|
-
let makeGetRowsQuery = (~pgSchema) =>
|
|
162
|
+
let makeGetRowsQuery = (~pgSchema, ~onlyConfig=false) =>
|
|
163
163
|
`SELECT "chain_id" as "chainId",
|
|
164
164
|
"address" as "address",
|
|
165
165
|
"contract_id" as "contractId",
|
|
166
166
|
"registration_block" as "registrationBlock"
|
|
167
|
-
FROM "${pgSchema}"."${table.tableName}"
|
|
167
|
+
FROM "${pgSchema}"."${table.tableName}"${onlyConfig
|
|
168
|
+
? ` WHERE "registration_block" = ${AddressRows.configRegistrationBlock->Int.toString}`
|
|
169
|
+
: ""};`
|
|
168
170
|
}
|
|
169
171
|
|
|
170
172
|
module Chains = {
|
|
@@ -391,6 +393,54 @@ WHERE "${(#id: field :> string)}" = $2
|
|
|
391
393
|
checkpointId: string,
|
|
392
394
|
}
|
|
393
395
|
|
|
396
|
+
let readStoredChains = async (sql, ~pgSchema): array<ResumePlan.storedChain> => {
|
|
397
|
+
let (rows, configAddressRows) = await Promise.all2((
|
|
398
|
+
sql
|
|
399
|
+
->Postgres.unsafe(
|
|
400
|
+
`SELECT "${(#id: field :> string)}" as "id",
|
|
401
|
+
"${(#ecosystem: field :> string)}" as "ecosystem",
|
|
402
|
+
"${(#start_block: field :> string)}" as "startBlock",
|
|
403
|
+
"${(#end_block: field :> string)}" as "endBlock",
|
|
404
|
+
"${(#max_reorg_depth: field :> string)}" as "maxReorgDepth"
|
|
405
|
+
FROM "${pgSchema}"."${table.tableName}";`,
|
|
406
|
+
)
|
|
407
|
+
->(
|
|
408
|
+
Utils.magic: promise<array<unknown>> => promise<
|
|
409
|
+
array<{
|
|
410
|
+
"id": ChainId.t,
|
|
411
|
+
"ecosystem": string,
|
|
412
|
+
"startBlock": int,
|
|
413
|
+
"endBlock": Null.t<int>,
|
|
414
|
+
"maxReorgDepth": int,
|
|
415
|
+
}>,
|
|
416
|
+
>
|
|
417
|
+
),
|
|
418
|
+
sql
|
|
419
|
+
->Postgres.unsafe(EnvioAddresses.makeGetRowsQuery(~pgSchema, ~onlyConfig=true))
|
|
420
|
+
->(Utils.magic: promise<array<unknown>> => promise<array<AddressRows.row>>),
|
|
421
|
+
))
|
|
422
|
+
let configAddressesByChain = Dict.make()
|
|
423
|
+
configAddressRows->Array.forEach(addressRow =>
|
|
424
|
+
configAddressesByChain->Utils.Dict.push(
|
|
425
|
+
addressRow.chainId->ChainId.normalizeOrThrow->ChainId.toString,
|
|
426
|
+
addressRow,
|
|
427
|
+
)
|
|
428
|
+
)
|
|
429
|
+
rows->Array.map((row): ResumePlan.storedChain => {
|
|
430
|
+
let id = row["id"]->ChainId.normalizeOrThrow
|
|
431
|
+
{
|
|
432
|
+
id,
|
|
433
|
+
ecosystem: row["ecosystem"],
|
|
434
|
+
startBlock: row["startBlock"],
|
|
435
|
+
endBlock: row["endBlock"]->Null.toOption,
|
|
436
|
+
maxReorgDepth: row["maxReorgDepth"],
|
|
437
|
+
configAddresses: configAddressesByChain
|
|
438
|
+
->Utils.Dict.dangerouslyGetNonOption(id->ChainId.toString)
|
|
439
|
+
->Option.getOr([]),
|
|
440
|
+
}
|
|
441
|
+
})
|
|
442
|
+
}
|
|
443
|
+
|
|
394
444
|
let makeGetInitialStateQuery = (~pgSchema) => {
|
|
395
445
|
`SELECT "${(#id: field :> string)}" as "id",
|
|
396
446
|
"${(#start_block: field :> string)}" as "startBlock",
|