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,26 +1,46 @@
1
1
  // Generated by ReScript, PLEASE EDIT WITH CARE
2
2
 
3
3
  import * as Url from "url";
4
+ import * as Core from "./Core.res.mjs";
4
5
  import * as Path from "path";
5
6
  import * as Utils from "./Utils.res.mjs";
6
7
  import * as Logging from "./Logging.res.mjs";
7
8
  import * as Process from "process";
8
- import * as Nodemodule from "node:module";
9
+ import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
9
10
  import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
10
11
  import * as Stdlib_Promise from "@rescript/runtime/lib/es6/Stdlib_Promise.js";
11
12
  import * as HandlerRegister from "./HandlerRegister.res.mjs";
12
13
  import * as Promises from "node:fs/promises";
14
+ import * as TsModuleHooksMjs from "./TsModuleHooks.mjs";
13
15
  import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
16
+ import * as HandlerTypeCheckMjs from "./HandlerTypeCheck.mjs";
14
17
 
15
- try {
16
- Nodemodule.register("tsx/esm", import.meta.url);
17
- } catch (exn) {
18
-
18
+ function registerTsHooksWith(prim) {
19
+ TsModuleHooksMjs.register(prim);
19
20
  }
20
21
 
21
- function toImportUrl(relativePath) {
22
- let absolutePath = Path.resolve(Process.cwd(), relativePath);
23
- return Url.pathToFileURL(absolutePath).toString();
22
+ function registerTsHooks() {
23
+ let addon = Core.getAddon();
24
+ TsModuleHooksMjs.register({
25
+ loadTs: addon.loadTs,
26
+ tsResolveCandidates: addon.tsResolveCandidates,
27
+ tsNotFoundCandidates: addon.tsNotFoundCandidates
28
+ });
29
+ }
30
+
31
+ function isTypeScript(file) {
32
+ return /\.m?tsx?$/.test(file);
33
+ }
34
+
35
+ function toAbsolutePath(file) {
36
+ return Path.resolve(Process.cwd(), file);
37
+ }
38
+
39
+ async function importHandler(file) {
40
+ if (isTypeScript(file)) {
41
+ Core.getAddon().tsCheckHandlerFormat(toAbsolutePath(file));
42
+ }
43
+ return await import(Url.pathToFileURL(toAbsolutePath(file)).toString());
24
44
  }
25
45
 
26
46
  async function registerContractHandlers(contractName, handler) {
@@ -28,7 +48,7 @@ async function registerContractHandlers(contractName, handler) {
28
48
  return;
29
49
  }
30
50
  try {
31
- await import(toImportUrl(handler));
51
+ await importHandler(handler);
32
52
  return;
33
53
  } catch (raw_exn) {
34
54
  let exn = Primitive_exceptions.internalToException(raw_exn);
@@ -38,35 +58,80 @@ async function registerContractHandlers(contractName, handler) {
38
58
  }
39
59
  }
40
60
 
41
- async function autoLoadFromSrcHandlers(handlers) {
61
+ async function globAutoLoadFiles(handlers) {
42
62
  let srcPattern = `./` + handlers + `/**/*.{js,mjs,ts}`;
43
- let handlerFiles;
44
63
  try {
45
64
  let iterator = Promises.glob(srcPattern);
46
65
  let files = await Utils.$$Array.fromAsyncIterator(iterator);
47
- handlerFiles = files.filter(file => !(file.includes(".test.") || file.includes(".spec.") || file.includes("_test.")));
66
+ return files.filter(file => !(file.includes(".test.") || file.includes(".spec.") || file.includes("_test.")));
48
67
  } catch (raw_exn) {
49
68
  let exn = Primitive_exceptions.internalToException(raw_exn);
50
- handlerFiles = Stdlib_JsError.throwWithMessage(`Failed to glob src/handlers directory for auto-loading handlers. Pattern: ` + srcPattern + `. Before continuing, check that you're using Node.js >=22 version. Error: ` + Utils.prettifyExn(exn));
69
+ return Stdlib_JsError.throwWithMessage(`Failed to glob src/handlers directory for auto-loading handlers. Pattern: ` + srcPattern + `. Error: ` + Utils.prettifyExn(exn));
70
+ }
71
+ }
72
+
73
+ async function getAutoLoadFiles(config) {
74
+ let match = config.subgraph;
75
+ if (match !== undefined) {
76
+ return [];
77
+ } else {
78
+ return await globAutoLoadFiles(config.handlers);
51
79
  }
52
- await Promise.all(handlerFiles.map(file => Stdlib_Promise.$$catch(import(toImportUrl(file)), exn => {
53
- let cause = Utils.prettifyExn(exn);
54
- Logging.errorWithExn(exn, `Failed to auto-load handler file: ` + file);
55
- return Stdlib_JsError.throwWithMessage(`Failed to auto-load handler file: ` + file + `. Cause: ` + cause);
56
- })));
57
80
  }
58
81
 
59
- async function registerAllHandlers(config) {
82
+ function checkTypesInWorker(prim0, prim1) {
83
+ return HandlerTypeCheckMjs.check(prim0, prim1);
84
+ }
85
+
86
+ async function typeCheck(config, autoLoadFiles) {
87
+ let files = Array.from(new Set(autoLoadFiles.concat(Stdlib_Array.filterMap(config.contractHandlers, param => param.handler)).filter(isTypeScript).map(toAbsolutePath)));
88
+ if (files.length === 0) {
89
+ return;
90
+ }
91
+ let match = await HandlerTypeCheckMjs.check(Process.cwd(), files);
92
+ let skipped = match.skipped;
93
+ let errors = match.errors;
94
+ if (errors !== undefined) {
95
+ return Stdlib_JsError.throwWithMessage(`Handler files have type errors:\n\n` + errors);
96
+ } else if (skipped !== undefined) {
97
+ return Logging.warn(skipped);
98
+ } else {
99
+ return;
100
+ }
101
+ }
102
+
103
+ let registerSubgraph = ((subgraph, isDev) =>
104
+ import("./subgraph/runtime.ts").then((m) => m.registerSubgraph({ ...subgraph, isDev })));
105
+
106
+ async function registerAllHandlers(config, autoLoadFiles) {
60
107
  HandlerRegister.startRegistration(config);
61
- await autoLoadFromSrcHandlers(config.handlers);
62
- await Promise.all(config.contractHandlers.map(param => registerContractHandlers(param.name, param.handler)));
108
+ registerTsHooks();
109
+ let subgraph = config.subgraph;
110
+ if (subgraph !== undefined) {
111
+ await registerSubgraph(subgraph, config.isDev);
112
+ } else {
113
+ await Promise.all(autoLoadFiles.map(file => Stdlib_Promise.$$catch(importHandler(file), exn => {
114
+ let cause = Utils.prettifyExn(exn);
115
+ Logging.errorWithExn(exn, `Failed to auto-load handler file: ` + file);
116
+ return Stdlib_JsError.throwWithMessage(`Failed to auto-load handler file: ` + file + `. Cause: ` + cause);
117
+ })));
118
+ await Promise.all(config.contractHandlers.map(param => registerContractHandlers(param.name, param.handler)));
119
+ }
63
120
  return HandlerRegister.finishRegistration(config);
64
121
  }
65
122
 
66
123
  export {
67
- toImportUrl,
124
+ registerTsHooksWith,
125
+ registerTsHooks,
126
+ isTypeScript,
127
+ toAbsolutePath,
128
+ importHandler,
68
129
  registerContractHandlers,
69
- autoLoadFromSrcHandlers,
130
+ globAutoLoadFiles,
131
+ getAutoLoadFiles,
132
+ checkTypesInWorker,
133
+ typeCheck,
134
+ registerSubgraph,
70
135
  registerAllHandlers,
71
136
  }
72
- /* Not a pure module */
137
+ /* url Not a pure module */
@@ -0,0 +1,14 @@
1
+ import { Worker } from "node:worker_threads";
2
+
3
+ // The compiler blocks the thread it runs on, so it gets its own and the
4
+ // database setup carries on meanwhile.
5
+ export const check = (cwd, files) =>
6
+ new Promise((resolve, reject) => {
7
+ const worker = new Worker(new URL("./HandlerTypeCheckWorker.mjs", import.meta.url), {
8
+ workerData: { cwd, files },
9
+ });
10
+ worker.once("message", resolve);
11
+ worker.once("error", reject);
12
+ // Settles nothing once the result has arrived.
13
+ worker.once("exit", () => reject(new Error("The handler type check exited without a result.")));
14
+ });
@@ -0,0 +1,68 @@
1
+ import { existsSync } from "node:fs";
2
+ import { createRequire } from "node:module";
3
+ import path from "node:path";
4
+ import { stripVTControlCharacters } from "node:util";
5
+ import { parentPort, workerData } from "node:worker_threads";
6
+
7
+ const check = ({ cwd, files }) => {
8
+ // The ReScript compiler checks a ReScript project's handlers, so it has no
9
+ // reason to set up TypeScript, and the warning would only be noise there.
10
+ // `rescript.json` at the root is what makes codegen treat it as ReScript.
11
+ const skip = (warning) => (existsSync(path.join(cwd, "rescript.json")) ? {} : { skipped: warning });
12
+
13
+ let ts;
14
+ try {
15
+ // The project's own compiler, so handlers are checked exactly as the
16
+ // user's editor and `tsc` check them.
17
+ ts = createRequire(path.join(cwd, "package.json"))("typescript");
18
+ } catch {
19
+ return skip("Skipped the handler type check: the project doesn't depend on typescript.");
20
+ }
21
+
22
+ const configPath = ts.findConfigFile(cwd, ts.sys.fileExists);
23
+ if (configPath === undefined) {
24
+ return skip(
25
+ "Skipped the handler type check: no tsconfig.json found. Add one to type-check handlers on start, like the one envio init creates."
26
+ );
27
+ }
28
+
29
+ const host = {
30
+ getCanonicalFileName: (file) => file,
31
+ getCurrentDirectory: () => cwd,
32
+ getNewLine: () => "\n",
33
+ };
34
+ const format = (diagnostics) =>
35
+ stripVTControlCharacters(ts.formatDiagnosticsWithColorAndContext(diagnostics, host)).trimEnd();
36
+
37
+ const parsed = ts.getParsedCommandLineOfConfigFile(
38
+ configPath,
39
+ { noEmit: true },
40
+ {
41
+ ...ts.sys,
42
+ onUnRecoverableConfigFileDiagnostic: (diagnostic) => {
43
+ throw new Error(format([diagnostic]));
44
+ },
45
+ }
46
+ );
47
+
48
+ // The tsconfig's files are roots, as tsc makes them, since any of them can
49
+ // declare globals a handler relies on. Only the handlers' errors count.
50
+ const handlerFiles = files.map((file) => path.resolve(cwd, file));
51
+ const program = ts.createProgram({
52
+ rootNames: [...parsed.fileNames, ...handlerFiles],
53
+ options: parsed.options,
54
+ projectReferences: parsed.projectReferences,
55
+ });
56
+
57
+ // Every file is a root, so each has a source file. Without one,
58
+ // `getSemanticDiagnostics` would check the whole program.
59
+ const diagnostics = handlerFiles
60
+ .map((file) => program.getSourceFile(file))
61
+ .flatMap((sourceFile) => [
62
+ ...program.getSyntacticDiagnostics(sourceFile),
63
+ ...program.getSemanticDiagnostics(sourceFile),
64
+ ]);
65
+ return diagnostics.length === 0 ? {} : { errors: format(diagnostics) };
66
+ };
67
+
68
+ parentPort.postMessage(check(workerData));
package/src/Internal.res CHANGED
@@ -491,7 +491,7 @@ type eventConfig = private {
491
491
  }
492
492
 
493
493
  type fuelEventKind =
494
- | LogData({logId: string, decode: string => eventParams})
494
+ | LogData({logId: string, abi: JSON.t})
495
495
  | Mint
496
496
  | Burn
497
497
  | Transfer
package/src/LoadLayer.res CHANGED
@@ -332,6 +332,44 @@ let loadEffect = (
332
332
  )
333
333
  }
334
334
 
335
+ // Keying an _in walks every value, so it's computed once and handed on rather
336
+ // than recomputed by each reader. Before answering "not in memory", the rows
337
+ // already loaded for other filters are checked: they may cover this one.
338
+ let indexFilter = (inMemTable, ~filter, ~table) => {
339
+ let filterKey = filter->EntityFilter.toString(~table)
340
+ if !(inMemTable->InMemoryTable.Entity.hasIndex)(filterKey) {
341
+ inMemTable->InMemoryTable.Entity.tryIndexFromLoadedValues(~filter, ~table)
342
+ }
343
+ filterKey
344
+ }
345
+
346
+ // A recorded absence is an answer too: `Some(None)` means the entity is known
347
+ // not to exist, so no load is needed to say so.
348
+ let getByIdInMemory = (
349
+ ~entityConfig: Internal.entityConfig,
350
+ ~scope,
351
+ ~indexerState,
352
+ ~entityId: string,
353
+ ) => {
354
+ let inMemTable = indexerState->InMemoryStore.getInMemTable(~entityConfig, ~scope)
355
+ inMemTable.latestEntityChangeById->Dict.has(entityId)
356
+ ? Some((inMemTable->InMemoryTable.Entity.getUnsafe)(entityId))
357
+ : None
358
+ }
359
+
360
+ let getByFilterInMemory = (
361
+ ~entityConfig: Internal.entityConfig,
362
+ ~scope,
363
+ ~indexerState,
364
+ ~filter: EntityFilter.t,
365
+ ) => {
366
+ let inMemTable = indexerState->InMemoryStore.getInMemTable(~entityConfig, ~scope)
367
+ let filterKey = inMemTable->indexFilter(~filter, ~table=entityConfig.table)
368
+ (inMemTable->InMemoryTable.Entity.hasIndex)(filterKey)
369
+ ? Some((inMemTable->InMemoryTable.Entity.getUnsafeOnIndex)(filterKey))
370
+ : None
371
+ }
372
+
335
373
  let loadByParsedFilter = (
336
374
  ~loadManager,
337
375
  ~persistence: Persistence.t,
@@ -425,13 +463,7 @@ let loadByParsedFilter = (
425
463
  )
426
464
  }
427
465
 
428
- // Keying an _in walks every value, so it's computed once here and handed to
429
- // the load manager rather than recomputed by the hasher.
430
- let filterKey = filter->EntityFilter.toString(~table=entityConfig.table)
431
-
432
- if !(inMemTable->InMemoryTable.Entity.hasIndex)(filterKey) {
433
- inMemTable->InMemoryTable.Entity.tryIndexFromLoadedValues(~filter, ~table=entityConfig.table)
434
- }
466
+ let filterKey = inMemTable->indexFilter(~filter, ~table=entityConfig.table)
435
467
 
436
468
  loadManager->LoadManager.call(
437
469
  ~key,
@@ -15,6 +15,7 @@ import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
15
15
  import * as ErrorHandling from "./ErrorHandling.res.mjs";
16
16
  import * as InMemoryStore from "./InMemoryStore.res.mjs";
17
17
  import * as InMemoryTable from "./InMemoryTable.res.mjs";
18
+ import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
18
19
  import * as S$RescriptSchema from "rescript-schema/src/S.res.mjs";
19
20
  import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
20
21
 
@@ -182,46 +183,69 @@ function loadEffect(loadManager, persistence, effect, effectArgs, scope, indexer
182
183
  return LoadManager.call(loadManager, effectArgs, key, load, args => args.cacheKey, shouldGroup, hash => InMemoryStore.hasEffectOutput(inMemTable, hash), hash => InMemoryStore.getEffectOutputUnsafe(inMemTable, hash));
183
184
  }
184
185
 
186
+ function indexFilter(inMemTable, filter, table) {
187
+ let filterKey = EntityFilter.toString(filter, table);
188
+ if (!InMemoryTable.Entity.hasIndex(inMemTable)(filterKey)) {
189
+ InMemoryTable.Entity.tryIndexFromLoadedValues(inMemTable, filter, table);
190
+ }
191
+ return filterKey;
192
+ }
193
+
194
+ function getByIdInMemory(entityConfig, scope, indexerState, entityId) {
195
+ let inMemTable = InMemoryStore.getInMemTable(indexerState, entityConfig, scope);
196
+ if (entityId in inMemTable.latestEntityChangeById) {
197
+ return Primitive_option.some(InMemoryTable.Entity.getUnsafe(inMemTable)(entityId));
198
+ }
199
+ }
200
+
201
+ function getByFilterInMemory(entityConfig, scope, indexerState, filter) {
202
+ let inMemTable = InMemoryStore.getInMemTable(indexerState, entityConfig, scope);
203
+ let filterKey = indexFilter(inMemTable, filter, entityConfig.table);
204
+ if (InMemoryTable.Entity.hasIndex(inMemTable)(filterKey)) {
205
+ return InMemoryTable.Entity.getUnsafeOnIndex(inMemTable)(filterKey);
206
+ }
207
+ }
208
+
209
+ function loadByParsedFilter(loadManager, persistence, entityConfig, scope, indexerState, shouldGroup, item, ecosystem, filter) {
210
+ let key = EntityFilter.toOperationKey(filter, entityConfig.name) + scopeKeySuffix(scope);
211
+ let inMemTable = InMemoryStore.getInMemTable(indexerState, entityConfig, scope);
212
+ let load = async (filters, param) => {
213
+ let storage = Persistence.getInitializedStorageOrThrow(persistence);
214
+ let timerRef = IndexerState.startStorageLoad(indexerState, storage.name, key);
215
+ let size = {
216
+ contents: 0
217
+ };
218
+ filters.forEach(filter => InMemoryTable.Entity.addEmptyIndex(inMemTable, filter, entityConfig.table));
219
+ await storage.ensureQueryIndexes(entityConfig, scope, filters);
220
+ let queries = EntityFilter.merge(filters);
221
+ await Promise.all(queries.map(async filter => {
222
+ try {
223
+ let entities = await storage.loadOrThrow(EntityFilter.scoped(filter, entityConfig.table, scope), entityConfig.table);
224
+ let committedCheckpointId = IndexerState.committedCheckpointIdFor(indexerState, scope);
225
+ entities.forEach(entity => InMemoryTable.Entity.initValue(inMemTable, committedCheckpointId, entity.id, entity));
226
+ InMemoryTable.Entity.recordLoadedValues(inMemTable, filter, entityConfig.table);
227
+ size.contents = size.contents + entities.length | 0;
228
+ return;
229
+ } catch (raw_exn) {
230
+ let exn = Primitive_exceptions.internalToException(raw_exn);
231
+ if (exn.RE_EXN_ID === Persistence.StorageError) {
232
+ return ErrorHandling.mkLogAndRaise(Logging.createChildFrom(Ecosystem.getItemLogger(item, ecosystem), {
233
+ operation: key,
234
+ params: EntityFilter.getParams(filter)
235
+ }), exn.message, exn.reason);
236
+ }
237
+ throw exn;
238
+ }
239
+ }));
240
+ return IndexerState.endStorageLoad(indexerState, timerRef, storage.name, key, Stdlib_Array.reduce(queries, 0, (acc, query) => acc + EntityFilter.valuesCount(query) | 0), size.contents);
241
+ };
242
+ let filterKey = indexFilter(inMemTable, filter, entityConfig.table);
243
+ return LoadManager.call(loadManager, filter, key, load, param => filterKey, shouldGroup, InMemoryTable.Entity.hasIndex(inMemTable), InMemoryTable.Entity.getUnsafeOnIndex(inMemTable));
244
+ }
245
+
185
246
  function loadByFilter(loadManager, persistence, entityConfig, scope, indexerState, shouldGroup, item, ecosystem, filter) {
186
247
  try {
187
- let filter$1 = EntityFilter.parseOrThrow(filter, entityConfig.name, entityConfig.table);
188
- let key = EntityFilter.toOperationKey(filter$1, entityConfig.name) + scopeKeySuffix(scope);
189
- let inMemTable = InMemoryStore.getInMemTable(indexerState, entityConfig, scope);
190
- let load = async (filters, param) => {
191
- let storage = Persistence.getInitializedStorageOrThrow(persistence);
192
- let timerRef = IndexerState.startStorageLoad(indexerState, storage.name, key);
193
- let size = {
194
- contents: 0
195
- };
196
- filters.forEach(filter => InMemoryTable.Entity.addEmptyIndex(inMemTable, filter, entityConfig.table));
197
- await storage.ensureQueryIndexes(entityConfig, scope, filters);
198
- let queries = EntityFilter.merge(filters);
199
- await Promise.all(queries.map(async filter => {
200
- try {
201
- let entities = await storage.loadOrThrow(EntityFilter.scoped(filter, entityConfig.table, scope), entityConfig.table);
202
- let committedCheckpointId = IndexerState.committedCheckpointIdFor(indexerState, scope);
203
- entities.forEach(entity => InMemoryTable.Entity.initValue(inMemTable, committedCheckpointId, entity.id, entity));
204
- InMemoryTable.Entity.recordLoadedValues(inMemTable, filter, entityConfig.table);
205
- size.contents = size.contents + entities.length | 0;
206
- return;
207
- } catch (raw_exn) {
208
- let exn = Primitive_exceptions.internalToException(raw_exn);
209
- if (exn.RE_EXN_ID === Persistence.StorageError) {
210
- return ErrorHandling.mkLogAndRaise(Logging.createChildFrom(Ecosystem.getItemLogger(item, ecosystem), {
211
- operation: key,
212
- params: EntityFilter.getParams(filter)
213
- }), exn.message, exn.reason);
214
- }
215
- throw exn;
216
- }
217
- }));
218
- return IndexerState.endStorageLoad(indexerState, timerRef, storage.name, key, Stdlib_Array.reduce(queries, 0, (acc, query) => acc + EntityFilter.valuesCount(query) | 0), size.contents);
219
- };
220
- let filterKey = EntityFilter.toString(filter$1, entityConfig.table);
221
- if (!InMemoryTable.Entity.hasIndex(inMemTable)(filterKey)) {
222
- InMemoryTable.Entity.tryIndexFromLoadedValues(inMemTable, filter$1, entityConfig.table);
223
- }
224
- return LoadManager.call(loadManager, filter$1, key, load, param => filterKey, shouldGroup, InMemoryTable.Entity.hasIndex(inMemTable), InMemoryTable.Entity.getUnsafeOnIndex(inMemTable));
248
+ return loadByParsedFilter(loadManager, persistence, entityConfig, scope, indexerState, shouldGroup, item, ecosystem, EntityFilter.parseOrThrow(filter, entityConfig.name, entityConfig.table));
225
249
  } catch (raw_exn) {
226
250
  let exn = Primitive_exceptions.internalToException(raw_exn);
227
251
  return Promise.reject(Utils.prettifyExn(exn));
@@ -231,6 +255,9 @@ function loadByFilter(loadManager, persistence, entityConfig, scope, indexerStat
231
255
  export {
232
256
  loadById,
233
257
  loadByFilter,
258
+ loadByParsedFilter,
259
+ getByIdInMemory,
260
+ getByFilterInMemory,
234
261
  loadEffect,
235
262
  }
236
263
  /* Utils Not a pure module */
@@ -22,6 +22,34 @@ let loadByFilter: (
22
22
  ~filter: dict<dict<unknown>>,
23
23
  ) => promise<array<Internal.entity>>
24
24
 
25
+ let loadByParsedFilter: (
26
+ ~loadManager: LoadManager.t,
27
+ ~persistence: Persistence.t,
28
+ ~entityConfig: Internal.entityConfig,
29
+ ~scope: Internal.chainScope,
30
+ ~indexerState: IndexerState.t,
31
+ ~shouldGroup: bool,
32
+ ~item: Internal.item,
33
+ ~ecosystem: Ecosystem.t,
34
+ ~filter: EntityFilter.t,
35
+ ) => promise<array<Internal.entity>>
36
+
37
+ // What a get or getWhere answers without a load, or None when it would need
38
+ // one. The sync context reads through these and suspends on None.
39
+ let getByIdInMemory: (
40
+ ~entityConfig: Internal.entityConfig,
41
+ ~scope: Internal.chainScope,
42
+ ~indexerState: IndexerState.t,
43
+ ~entityId: string,
44
+ ) => option<option<Internal.entity>>
45
+
46
+ let getByFilterInMemory: (
47
+ ~entityConfig: Internal.entityConfig,
48
+ ~scope: Internal.chainScope,
49
+ ~indexerState: IndexerState.t,
50
+ ~filter: EntityFilter.t,
51
+ ) => option<array<Internal.entity>>
52
+
25
53
  let loadEffect: (
26
54
  ~loadManager: LoadManager.t,
27
55
  ~persistence: Persistence.t,
package/src/Main.res CHANGED
@@ -411,8 +411,6 @@ let getGlobalIndexer = (): 'indexer => {
411
411
  Utils.Proxy.make(Utils.Object.createNullObject(), traps)->(Utils.magic: {..} => 'indexer)
412
412
  }
413
413
 
414
- // The RPC-stripped public config that the storage layer persists in
415
- // `envio_info` (on initialize) and validates against (on resume).
416
414
  let migrate = async (~reset) => {
417
415
  let config = Config.load()
418
416
  let persistence = PgStorage.makePersistenceFromConfig(~config)
@@ -420,8 +418,8 @@ let migrate = async (~reset) => {
420
418
  ~reset,
421
419
  ~chainConfigs=config.chainMap->ChainMap.values,
422
420
  ~contractMapping=config.contractMapping,
423
- ~envioInfo=Config.envioInfo(),
424
- ~resetCommand="envio local db-migrate setup",
421
+ ~envioInfo=config.envioInfo,
422
+ ~resetCommand="envio local db-migrate setup"->Config.withProjectFlags(~config),
425
423
  ~runCommand=None,
426
424
  ~lowercaseAddresses=config.lowercaseAddresses,
427
425
  // A migration command runs once and exits, with nobody watching it recover:
@@ -466,18 +464,23 @@ exception FatalError(exn)
466
464
  | None => PgStorage.makePersistenceFromConfig(~config)
467
465
  }
468
466
  setGlobalPersistence(persistence)
469
- await persistence->Persistence.initForRun(
470
- ~config,
471
- ~reset,
472
- ~isDevelopmentMode,
473
- ~requireInitialized=config.isolated,
474
- )
467
+ let autoLoadFiles = await HandlerLoader.getAutoLoadFiles(~config)
468
+ // A worker's supervisor has checked the handlers already, and the test
469
+ // indexer leaves type checking to the user's test setup.
470
+ let typeCheck =
471
+ isTest || Worker.isEnabled
472
+ ? Promise.resolve()
473
+ : HandlerLoader.typeCheck(~config, ~autoLoadFiles)
474
+ let _ = await Promise.all2((
475
+ persistence->Persistence.initForRun(~config, ~reset, ~isDevelopmentMode),
476
+ typeCheck,
477
+ ))
475
478
 
476
479
  // Loads user handler files, which register handler/contractRegister/where
477
480
  // state into the global `HandlerRegister` registry as a side effect; this
478
481
  // returns that state resolved into per-chain registrations. `config` itself
479
482
  // is never mutated by registration — it holds only event definitions.
480
- let registrationsByChainId = await HandlerLoader.registerAllHandlers(~config)
483
+ let registrationsByChainId = await HandlerLoader.registerAllHandlers(~config, ~autoLoadFiles)
481
484
  let config = if isTest {
482
485
  {...config, shouldRollbackOnReorg: false}
483
486
  } else {
@@ -535,7 +538,7 @@ exception FatalError(exn)
535
538
  ~onError,
536
539
  )
537
540
  if shouldUseTui {
538
- let _rerender = Tui.start(~config, ~getMetrics=() => state->IndexerState.toMetrics)
541
+ Tui.start(~config, ~getMetrics=() => state->IndexerState.toMetrics)
539
542
  }
540
543
  Worker.bindRun(
541
544
  ~getMetrics=() => state->IndexerState.toMetrics,
@@ -565,6 +568,8 @@ let start = async (
565
568
  Worker.config->Option.forEach(({chainIds, isDev}) =>
566
569
  Config.prime(Config.getPublicConfigJson()->Config.withCommandFields(~chainIds, ~isDev))
567
570
  )
571
+ // An unsupported Node fails here, before the database is touched.
572
+ HandlerLoader.registerTsHooks()
568
573
  let config = Config.load()
569
574
  switch isTest ? None : Supervisor.planForRun(~config) {
570
575
  | Some(workers) => await Supervisor.run(~config, ~workers, ~reset)
package/src/Main.res.mjs CHANGED
@@ -351,7 +351,7 @@ function getGlobalIndexer() {
351
351
  async function migrate(reset) {
352
352
  let config = Config.load();
353
353
  let persistence = PgStorage.makePersistenceFromConfig(config, undefined);
354
- await Persistence.init(persistence, ChainMap.values(config.chainMap), config.contractMapping, Config.envioInfo(), "envio local db-migrate setup", undefined, reset, config.lowercaseAddresses, undefined, undefined, "Once");
354
+ await Persistence.init(persistence, ChainMap.values(config.chainMap), config.contractMapping, config.envioInfo, Config.withProjectFlags("envio local db-migrate setup", config), undefined, reset, config.lowercaseAddresses, undefined, undefined, "Once");
355
355
  return await persistence.storage.close();
356
356
  }
357
357
 
@@ -372,8 +372,13 @@ async function startIndexer(config, persistence, resetOpt, isTestOpt, exitAfterF
372
372
  let isDevelopmentMode = !isTest && config.isDev;
373
373
  let persistence$1 = persistence !== undefined ? persistence : PgStorage.makePersistenceFromConfig(config, undefined);
374
374
  EnvioGlobal.value.persistence = Primitive_option.some(persistence$1);
375
- await Persistence.initForRun(persistence$1, config, reset, isDevelopmentMode, config.isolated);
376
- let registrationsByChainId = await HandlerLoader.registerAllHandlers(config);
375
+ let autoLoadFiles = await HandlerLoader.getAutoLoadFiles(config);
376
+ let typeCheck = isTest || Worker.isEnabled ? Promise.resolve() : HandlerLoader.typeCheck(config, autoLoadFiles);
377
+ await Promise.all([
378
+ Persistence.initForRun(persistence$1, config, reset, isDevelopmentMode),
379
+ typeCheck
380
+ ]);
381
+ let registrationsByChainId = await HandlerLoader.registerAllHandlers(config, autoLoadFiles);
377
382
  let config$1;
378
383
  if (isTest) {
379
384
  let newrecord = {...config};
@@ -417,6 +422,7 @@ async function start(persistence, resetOpt, isTestOpt, exitAfterFirstEventBlockO
417
422
  let isTest = isTestOpt !== undefined ? isTestOpt : false;
418
423
  let exitAfterFirstEventBlock = exitAfterFirstEventBlockOpt !== undefined ? exitAfterFirstEventBlockOpt : false;
419
424
  Stdlib_Option.forEach(Worker.config, param => Config.prime(Config.withCommandFields(Config.getPublicConfigJson(), param.chainIds, param.isDev)));
425
+ HandlerLoader.registerTsHooks();
420
426
  let config = Config.load();
421
427
  let workers = isTest ? undefined : Supervisor.planForRun(config, undefined);
422
428
  if (workers !== undefined) {
@@ -0,0 +1,13 @@
1
+ const MINIMUM = [22, 15, 0];
2
+
3
+ // The message for a Node.js older than envio supports, or undefined.
4
+ export const unsupportedNodeMessage = () => {
5
+ const current = process.versions.node;
6
+ const parts = current.split(".").map(Number);
7
+ const index = MINIMUM.findIndex((minimum, i) => parts[i] !== minimum);
8
+ if (index === -1 || parts[index] > MINIMUM[index]) return undefined;
9
+ return [
10
+ `envio needs Node.js ${MINIMUM.join(".")} or newer, and this is Node.js ${current}.`,
11
+ "Install a newer one from https://nodejs.org/en/download, or with your version manager, for example: nvm install --lts",
12
+ ].join("\n");
13
+ };