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.
Files changed (111) hide show
  1. package/bin.mjs +10 -1
  2. package/index.d.ts +11 -0
  3. package/package.json +8 -20
  4. package/rescript.json +1 -4
  5. package/src/Config.res +78 -100
  6. package/src/Config.res.mjs +55 -64
  7. package/src/Core.res +67 -6
  8. package/src/Core.res.mjs +58 -4
  9. package/src/EventConfigBuilder.res +4 -10
  10. package/src/EventConfigBuilder.res.mjs +1 -3
  11. package/src/EventProcessing.res +6 -6
  12. package/src/EventProcessing.res.mjs +8 -6
  13. package/src/ExitOnCaughtUp.res +5 -1
  14. package/src/ExitOnCaughtUp.res.mjs +8 -3
  15. package/src/HandlerLoader.res +98 -36
  16. package/src/HandlerLoader.res.mjs +89 -24
  17. package/src/HandlerTypeCheck.mjs +14 -0
  18. package/src/HandlerTypeCheckWorker.mjs +68 -0
  19. package/src/Internal.res +1 -1
  20. package/src/LoadLayer.res +39 -7
  21. package/src/LoadLayer.res.mjs +65 -38
  22. package/src/LoadLayer.resi +28 -0
  23. package/src/Main.res +17 -12
  24. package/src/Main.res.mjs +9 -3
  25. package/src/NodeVersion.mjs +13 -0
  26. package/src/Persistence.res +79 -38
  27. package/src/Persistence.res.mjs +41 -10
  28. package/src/PgStorage.res +86 -39
  29. package/src/PgStorage.res.mjs +83 -32
  30. package/src/ResumePlan.res +172 -0
  31. package/src/ResumePlan.res.mjs +122 -0
  32. package/src/Supervisor.res +8 -8
  33. package/src/Supervisor.res.mjs +6 -1
  34. package/src/SyncWrites.res +86 -0
  35. package/src/SyncWrites.res.mjs +85 -0
  36. package/src/TestIndexer.res +14 -4
  37. package/src/TestIndexer.res.mjs +3 -2
  38. package/src/TsModuleHooks.mjs +121 -0
  39. package/src/UserContext.res +377 -62
  40. package/src/UserContext.res.mjs +297 -39
  41. package/src/Utils.res +0 -8
  42. package/src/Worker.res +8 -0
  43. package/src/Worker.res.mjs +11 -0
  44. package/src/bindings/NodeJs.res +8 -7
  45. package/src/bindings/NodeJs.res.mjs +0 -3
  46. package/src/bindings/Pino.res +2 -2
  47. package/src/db/InternalTable.res +52 -2
  48. package/src/db/InternalTable.res.mjs +32 -3
  49. package/src/db/Table.res +10 -0
  50. package/src/db/Table.res.mjs +22 -11
  51. package/src/sources/FuelHyperSync.res +2 -0
  52. package/src/sources/FuelHyperSync.res.mjs +1 -0
  53. package/src/sources/FuelHyperSync.resi +1 -0
  54. package/src/sources/FuelHyperSyncClient.res +25 -8
  55. package/src/sources/FuelHyperSyncClient.res.mjs +8 -2
  56. package/src/sources/FuelHyperSyncSource.res +24 -22
  57. package/src/sources/FuelHyperSyncSource.res.mjs +18 -13
  58. package/src/subgraph/abi-types.ts +63 -0
  59. package/src/subgraph/assemblyscript.ts +85 -0
  60. package/src/subgraph/blocks.ts +183 -0
  61. package/src/subgraph/calls.ts +149 -0
  62. package/src/subgraph/conformance.ts +83 -0
  63. package/src/subgraph/errors.ts +96 -0
  64. package/src/subgraph/graph-cli-worker.mjs +26 -0
  65. package/src/subgraph/graph-cli.ts +190 -0
  66. package/src/subgraph/graph-ts-enums.ts +28 -0
  67. package/src/subgraph/graph-ts-types/VERSION +2 -0
  68. package/src/subgraph/graph-ts-types/chain/arweave.d.ts +70 -0
  69. package/src/subgraph/graph-ts-types/chain/cosmos.d.ts +327 -0
  70. package/src/subgraph/graph-ts-types/chain/ethereum.d.ts +233 -0
  71. package/src/subgraph/graph-ts-types/chain/near.d.ts +253 -0
  72. package/src/subgraph/graph-ts-types/chain/starknet.d.ts +32 -0
  73. package/src/subgraph/graph-ts-types/common/collections.d.ts +136 -0
  74. package/src/subgraph/graph-ts-types/common/conversion.d.ts +11 -0
  75. package/src/subgraph/graph-ts-types/common/datasource.d.ts +30 -0
  76. package/src/subgraph/graph-ts-types/common/eager-offset.d.ts +0 -0
  77. package/src/subgraph/graph-ts-types/common/json.d.ts +17 -0
  78. package/src/subgraph/graph-ts-types/common/numbers.d.ts +120 -0
  79. package/src/subgraph/graph-ts-types/common/value.d.ts +120 -0
  80. package/src/subgraph/graph-ts-types/common/yaml.d.ts +90 -0
  81. package/src/subgraph/graph-ts-types/global/global.d.ts +194 -0
  82. package/src/subgraph/graph-ts-types/helper-functions.d.ts +22 -0
  83. package/src/subgraph/graph-ts-types/index.d.ts +102 -0
  84. package/src/subgraph/graph-ts.ts +2285 -0
  85. package/src/subgraph/hosts.ts +158 -0
  86. package/src/subgraph/rpc.ts +65 -0
  87. package/src/subgraph/runtime.ts +595 -0
  88. package/src/subgraph/scope.ts +68 -0
  89. package/src/tui/InitApi.res +85 -0
  90. package/src/tui/{components/CustomHooks.res.mjs → InitApi.res.mjs} +11 -64
  91. package/src/tui/Tui.res +142 -249
  92. package/src/tui/Tui.res.mjs +123 -332
  93. package/src/Js.shim.ts +0 -11
  94. package/src/bindings/DateFns.res +0 -74
  95. package/src/bindings/DateFns.res.mjs +0 -22
  96. package/src/sources/FuelSDK.res +0 -38
  97. package/src/sources/FuelSDK.res.mjs +0 -27
  98. package/src/sources/vendored-fuel-abi-coder.js +0 -1792
  99. package/src/tui/bindings/Ink.res +0 -369
  100. package/src/tui/bindings/Ink.res.mjs +0 -66
  101. package/src/tui/bindings/Style.res +0 -123
  102. package/src/tui/bindings/Style.res.mjs +0 -2
  103. package/src/tui/components/BufferedProgressBar.res +0 -40
  104. package/src/tui/components/BufferedProgressBar.res.mjs +0 -57
  105. package/src/tui/components/CustomHooks.res +0 -122
  106. package/src/tui/components/Messages.res +0 -40
  107. package/src/tui/components/Messages.res.mjs +0 -71
  108. package/src/tui/components/SyncETA.res +0 -179
  109. package/src/tui/components/SyncETA.res.mjs +0 -249
  110. package/src/tui/components/TuiData.res +0 -98
  111. package/src/tui/components/TuiData.res.mjs +0 -73
@@ -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 initEffect(params) {
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 makeCaller = caller => ((effect, input) => {
54
- let scope = isCrossChain(effect) ? "crossChain" : handlerChainId;
55
- if (caller !== undefined && isCrossChain(caller) && !isCrossChain(effect)) {
56
- 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\`).`);
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 tmp;
59
- tmp = scope === "crossChain" ? undefined : Primitive_option.some(scope);
60
- let effectContext = new EffectContext(params, tmp, effect.name, effect.defaultShouldCache, makeCaller(effect));
61
- let effectArgs_cacheKey = Utils.Hash.makeOrThrow(S$RescriptSchema.reverseConvertOrThrow(input, effect.input));
62
- let effectArgs_checkpointId = params.checkpointId;
63
- let effectArgs = {
64
- input: input,
65
- context: effectContext,
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 scope = entityScope(params);
98
- let committedCheckpointId = IndexerState.committedCheckpointIdFor(params.indexerState, scope);
99
- let set = params.isPreload ? noopSet : entity => InMemoryTable.Entity.set(InMemoryStore.getInMemTable(params.indexerState, params.entityConfig, scope), committedCheckpointId, {
100
- type: "SET",
101
- entityId: entity.id,
102
- entity: entity,
103
- checkpointId: params.checkpointId
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 => InMemoryTable.Entity.set(InMemoryStore.getInMemTable(params.indexerState, params.entityConfig, scope), committedCheckpointId, {
111
- type: "DELETE",
112
- entityId: entityId,
113
- checkpointId: params.checkpointId
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
- let handlerTraps_get = (params, prop) => {
165
- if (params.isResolved) {
166
- ErrorHandling.mkLogAndRaise(Ecosystem.getItemLogger(params.item, params.config.ecosystem), undefined, new Error(`Impossible to access context.` + prop + ` after the handler is resolved. Make sure you didn't miss an await in the handler.`));
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
- isResolved: params.isResolved,
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.
@@ -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 */
@@ -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,
@@ -113,8 +113,8 @@ module ECS = {
113
113
  }
114
114
 
115
115
  /**
116
- Jank solution to make logs use console log wrather than stream.write so that ink
117
- can render the logs statically.
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}
@@ -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",