envio 3.7.0 → 3.9.0-resolvers.0
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/README.md +2 -2
- package/evm.schema.json +7 -0
- package/fuel.schema.json +7 -0
- package/index.d.ts +674 -161
- package/index.js +10 -0
- package/package.json +6 -7
- package/src/AddressRows.res +121 -0
- package/src/AddressRows.res.mjs +143 -0
- package/src/Batch.res +11 -0
- package/src/Batch.res.mjs +2 -1
- package/src/Bin.res +47 -0
- package/src/Bin.res.mjs +34 -1
- package/src/ChainState.res +108 -98
- package/src/ChainState.res.mjs +86 -67
- package/src/ChainState.resi +28 -15
- package/src/Config.res +61 -131
- package/src/Config.res.mjs +47 -79
- package/src/ContractMapping.res +66 -0
- package/src/ContractMapping.res.mjs +73 -0
- package/src/Core.res +24 -0
- package/src/Core.res.mjs +4 -0
- package/src/Ecosystem.res +7 -2
- package/src/Env.res +128 -1
- package/src/Env.res.mjs +123 -1
- package/src/Envio.res +54 -76
- package/src/EventConfigBuilder.res +316 -46
- package/src/EventConfigBuilder.res.mjs +231 -28
- package/src/FetchState.res +59 -67
- package/src/FetchState.res.mjs +34 -44
- package/src/HandlerRegister.res +26 -15
- package/src/HandlerRegister.res.mjs +27 -7
- package/src/Hasura.res +30 -22
- package/src/Hasura.res.mjs +11 -5
- package/src/InMemoryStore.res +23 -38
- package/src/InMemoryStore.res.mjs +13 -27
- package/src/IndexerState.res +40 -3
- package/src/IndexerState.res.mjs +30 -4
- package/src/IndexerState.resi +4 -1
- package/src/Internal.res +49 -30
- package/src/Internal.res.mjs +12 -16
- package/src/Main.res +27 -20
- package/src/Main.res.mjs +19 -14
- package/src/Metrics.res +15 -1
- package/src/Metrics.res.mjs +5 -1
- package/src/Persistence.res +46 -21
- package/src/Persistence.res.mjs +8 -5
- package/src/PgStorage.res +238 -79
- package/src/PgStorage.res.mjs +115 -73
- package/src/PruneStaleHistory.res +1 -1
- package/src/PruneStaleHistory.res.mjs +1 -1
- package/src/Rollback.res +62 -41
- package/src/Rollback.res.mjs +61 -19
- package/src/RollbackCommit.res +7 -9
- package/src/RollbackCommit.res.mjs +5 -6
- package/src/SimulateItems.res +254 -9
- package/src/SimulateItems.res.mjs +214 -47
- package/src/Sink.res +35 -26
- package/src/Sink.res.mjs +20 -16
- package/src/TestIndexer.res +121 -123
- package/src/TestIndexer.res.mjs +97 -102
- package/src/Utils.res +38 -0
- package/src/Utils.res.mjs +39 -2
- package/src/Writing.res +4 -2
- package/src/Writing.res.mjs +3 -2
- package/src/bindings/ClickHouse.res +311 -656
- package/src/bindings/ClickHouse.res.mjs +294 -395
- package/src/bindings/ClickHouseSink.res +303 -0
- package/src/bindings/ClickHouseSink.res.mjs +216 -0
- package/src/bindings/NodeJs.res +9 -0
- package/src/bindings/NodeJs.res.mjs +8 -1
- package/src/bindings/Postgres.res +9 -0
- package/src/bindings/Postgres.res.mjs +3 -0
- package/src/db/EntityHistory.res +29 -24
- package/src/db/EntityHistory.res.mjs +11 -19
- package/src/db/InternalTable.res +258 -83
- package/src/db/InternalTable.res.mjs +223 -85
- package/src/db/RollbackScope.res +29 -0
- package/src/db/RollbackScope.res.mjs +31 -0
- package/src/db/Table.res +28 -1
- package/src/db/Table.res.mjs +26 -2
- package/src/resolvers/ResolverProcess.res +809 -0
- package/src/resolvers/ResolverProcess.res.mjs +667 -0
- package/src/resolvers/ResolverQuery.res +317 -0
- package/src/resolvers/ResolverQuery.res.mjs +220 -0
- package/src/resolvers/collisions.js +108 -0
- package/src/resolvers/db.js +363 -0
- package/src/resolvers/dispatch.js +165 -0
- package/src/resolvers/errors.js +30 -0
- package/src/resolvers/graphqlSelection.js +292 -0
- package/src/resolvers/hasuraAction.js +84 -0
- package/src/resolvers/hasuraApply.js +323 -0
- package/src/resolvers/hasuraMetadata.js +119 -0
- package/src/resolvers/index.js +95 -0
- package/src/resolvers/manifest.js +370 -0
- package/src/resolvers/server.js +270 -0
- package/src/sources/AddressSet.res +0 -22
- package/src/sources/AddressStore.res +48 -88
- package/src/sources/AddressStore.res.mjs +11 -22
- package/src/sources/Evm.res +2 -0
- package/src/sources/Evm.res.mjs +2 -0
- package/src/sources/EvmHyperSyncSource.res +3 -2
- package/src/sources/Fuel.res +2 -0
- package/src/sources/Fuel.res.mjs +2 -0
- package/src/sources/SimulateSource.res +8 -4
- package/src/sources/SimulateSource.res.mjs +7 -3
- package/src/sources/Svm.res +36 -7
- package/src/sources/Svm.res.mjs +34 -4
- package/src/sources/SvmHyperSyncClient.res +20 -116
- package/src/sources/SvmHyperSyncClient.res.mjs +5 -7
- package/src/sources/SvmHyperSyncSource.res +89 -37
- package/src/sources/SvmHyperSyncSource.res.mjs +71 -39
- package/src/sources/TransactionStore.res +38 -0
- package/src/sources/TransactionStore.res.mjs +5 -0
- package/src/tui/Tui.res +1 -1
- package/src/tui/Tui.res.mjs +1 -1
- package/svm.schema.json +44 -156
- package/src/MemoryStorage.res +0 -724
- package/src/MemoryStorage.res.mjs +0 -601
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// The contract name <-> id mapping every stored address row references: a
|
|
2
|
+
// name's position in `names` is the id `envio_contracts` holds and
|
|
3
|
+
// `envio_addresses.contract_id` points at. Ids must mean the same thing on
|
|
4
|
+
// every chain and across restarts, so a mapping is built once — from the whole
|
|
5
|
+
// config, never from a filtered subset — and read everywhere else.
|
|
6
|
+
type t = {
|
|
7
|
+
// Id order. Index i is the name of contract id i.
|
|
8
|
+
names: array<string>,
|
|
9
|
+
idByName: dict<int>,
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
let indexNames = (names: array<string>): t => {
|
|
13
|
+
let idByName = Dict.make()
|
|
14
|
+
names->Array.forEachWithIndex((name, id) => idByName->Dict.set(name, id))
|
|
15
|
+
{names, idByName}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Ids are 0-based positions in a smallint column, so 32768 contracts fit.
|
|
19
|
+
let maxContracts = 32768
|
|
20
|
+
|
|
21
|
+
// Names in any order; the codec puts them in byte order so the ids never depend
|
|
22
|
+
// on the order contracts happen to be declared in.
|
|
23
|
+
let make = (~names: array<string>): t => {
|
|
24
|
+
let canonical = Core.getAddon().canonicalContractNames(names)
|
|
25
|
+
if canonical->Array.length > maxContracts {
|
|
26
|
+
JsError.throwWithMessage(
|
|
27
|
+
`The indexer declares ${canonical
|
|
28
|
+
->Array.length
|
|
29
|
+
->Int.toString} contracts, more than the ${maxContracts->Int.toString} a smallint contract id can hold.`,
|
|
30
|
+
)
|
|
31
|
+
}
|
|
32
|
+
indexNames(canonical)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// What a storage holds before it has been initialized: no contract has an id
|
|
36
|
+
// yet, so every lookup fails rather than resolving against a stale mapping.
|
|
37
|
+
let empty = indexNames([])
|
|
38
|
+
|
|
39
|
+
// A mapping read back from storage. Taken verbatim: the stored order *is* the
|
|
40
|
+
// id order the stored rows were written against, so re-canonicalizing it would
|
|
41
|
+
// paper over a mapping that no longer matches those rows.
|
|
42
|
+
let fromStoredNames = indexNames
|
|
43
|
+
|
|
44
|
+
let names = (mapping: t) => mapping.names
|
|
45
|
+
|
|
46
|
+
let idOfOrThrow = (mapping: t, name, ~context="") =>
|
|
47
|
+
switch mapping.idByName->Utils.Dict.dangerouslyGetNonOption(name) {
|
|
48
|
+
| Some(id) => id
|
|
49
|
+
| None =>
|
|
50
|
+
JsError.throwWithMessage(
|
|
51
|
+
`Contract "${name}"${context} is missing from the indexer's contract list.`,
|
|
52
|
+
)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
let nameOfOrThrow = (mapping: t, id) =>
|
|
56
|
+
switch mapping.names->Array.get(id) {
|
|
57
|
+
| Some(name) => name
|
|
58
|
+
| None =>
|
|
59
|
+
JsError.throwWithMessage(
|
|
60
|
+
`Contract id ${id->Int.toString} is outside the indexer's contract list.`,
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
let isEqual = (a: t, b: t) =>
|
|
65
|
+
a.names->Array.length === b.names->Array.length &&
|
|
66
|
+
a.names->Array.everyWithIndex((name, idx) => b.names->Array.getUnsafe(idx) === name)
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as Core from "./Core.res.mjs";
|
|
4
|
+
import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
|
|
5
|
+
|
|
6
|
+
function indexNames(names) {
|
|
7
|
+
let idByName = {};
|
|
8
|
+
names.forEach((name, id) => {
|
|
9
|
+
idByName[name] = id;
|
|
10
|
+
});
|
|
11
|
+
return {
|
|
12
|
+
names: names,
|
|
13
|
+
idByName: idByName
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function make(names) {
|
|
18
|
+
let canonical = Core.getAddon().canonicalContractNames(names);
|
|
19
|
+
if (canonical.length > 32768) {
|
|
20
|
+
Stdlib_JsError.throwWithMessage(`The indexer declares ` + canonical.length.toString() + ` contracts, more than the ` + (32768).toString() + ` a smallint contract id can hold.`);
|
|
21
|
+
}
|
|
22
|
+
return indexNames(canonical);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
let empty = indexNames([]);
|
|
26
|
+
|
|
27
|
+
function names(mapping) {
|
|
28
|
+
return mapping.names;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function idOfOrThrow(mapping, name, contextOpt) {
|
|
32
|
+
let context = contextOpt !== undefined ? contextOpt : "";
|
|
33
|
+
let id = mapping.idByName[name];
|
|
34
|
+
if (id !== undefined) {
|
|
35
|
+
return id;
|
|
36
|
+
} else {
|
|
37
|
+
return Stdlib_JsError.throwWithMessage(`Contract "` + name + `"` + context + ` is missing from the indexer's contract list.`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function nameOfOrThrow(mapping, id) {
|
|
42
|
+
let name = mapping.names[id];
|
|
43
|
+
if (name !== undefined) {
|
|
44
|
+
return name;
|
|
45
|
+
} else {
|
|
46
|
+
return Stdlib_JsError.throwWithMessage(`Contract id ` + id.toString() + ` is outside the indexer's contract list.`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function isEqual(a, b) {
|
|
51
|
+
if (a.names.length === b.names.length) {
|
|
52
|
+
return a.names.every((name, idx) => b.names[idx] === name);
|
|
53
|
+
} else {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
let maxContracts = 32768;
|
|
59
|
+
|
|
60
|
+
let fromStoredNames = indexNames;
|
|
61
|
+
|
|
62
|
+
export {
|
|
63
|
+
indexNames,
|
|
64
|
+
maxContracts,
|
|
65
|
+
make,
|
|
66
|
+
empty,
|
|
67
|
+
fromStoredNames,
|
|
68
|
+
names,
|
|
69
|
+
idOfOrThrow,
|
|
70
|
+
nameOfOrThrow,
|
|
71
|
+
isEqual,
|
|
72
|
+
}
|
|
73
|
+
/* empty Not a pure module */
|
package/src/Core.res
CHANGED
|
@@ -10,6 +10,7 @@ type svmHyperSyncClientCtor
|
|
|
10
10
|
type fuelHyperSyncClientCtor
|
|
11
11
|
type transactionStoreCtor
|
|
12
12
|
type blockStoreCtor
|
|
13
|
+
type clickHouseSinkCtor
|
|
13
14
|
type addressStoreCtor
|
|
14
15
|
// Test-only: a local HyperSync server, bound by MockHyperSyncServer in envio-tests.
|
|
15
16
|
type mockHyperSyncServerCtor
|
|
@@ -29,6 +30,7 @@ type fromUserApiResult = {
|
|
|
29
30
|
type addon = {
|
|
30
31
|
getConfigJson: (~configPath: Null.t<string>, ~directory: Null.t<string>) => string,
|
|
31
32
|
encodeIndexedTopic: (~abiType: string, ~value: unknown) => EvmTypes.Hex.t,
|
|
33
|
+
isSvmPubkey: (~value: string) => bool,
|
|
32
34
|
fromUserApi: (string, fromUserApiOptions) => fromUserApiResult,
|
|
33
35
|
runCli: (~args: array<string>, ~envioPackageDir: Null.t<string>) => promise<Null.t<string>>,
|
|
34
36
|
@as("EvmHyperSyncClient")
|
|
@@ -45,8 +47,26 @@ type addon = {
|
|
|
45
47
|
blockStore: blockStoreCtor,
|
|
46
48
|
@as("AddressStore")
|
|
47
49
|
addressStore: addressStoreCtor,
|
|
50
|
+
@as("ClickHouseSink")
|
|
51
|
+
clickHouseSink: clickHouseSinkCtor,
|
|
48
52
|
@as("MockHyperSyncServer")
|
|
49
53
|
mockHyperSyncServer: mockHyperSyncServerCtor,
|
|
54
|
+
encodeAddresses: (~ecosystem: string, ~addresses: array<Address.t>) => array<NodeJs.Buffer.t>,
|
|
55
|
+
renderAddresses: (
|
|
56
|
+
~ecosystem: string,
|
|
57
|
+
~shouldChecksum: bool,
|
|
58
|
+
~bytes: NodeJs.Buffer.t,
|
|
59
|
+
~lengths: array<int>,
|
|
60
|
+
) => array<Address.t>,
|
|
61
|
+
renderContractAddresses: (
|
|
62
|
+
~ecosystem: string,
|
|
63
|
+
~shouldChecksum: bool,
|
|
64
|
+
~bytes: NodeJs.Buffer.t,
|
|
65
|
+
~lengths: array<int>,
|
|
66
|
+
~contractIds: array<int>,
|
|
67
|
+
~contractId: int,
|
|
68
|
+
) => array<Address.t>,
|
|
69
|
+
canonicalContractNames: array<string> => array<string>,
|
|
50
70
|
// Ordered transaction-field names exposed for the field-code contract test
|
|
51
71
|
// (the ReScript `transactionFields` arrays must match the Rust ordinals).
|
|
52
72
|
evmTransactionFieldNames: unit => array<string>,
|
|
@@ -84,6 +104,10 @@ let loadDevAddon: ({..}, string) => addon = %raw(`function(req, envioDir) {
|
|
|
84
104
|
var path = Nodepath;
|
|
85
105
|
var fs = Nodefs;
|
|
86
106
|
|
|
107
|
+
// Vitest test.env points workers at the addon globalSetup already built.
|
|
108
|
+
var preBuilt = process.env.ENVIO_DEV_ADDON;
|
|
109
|
+
if (preBuilt && fs.existsSync(preBuilt)) return req(preBuilt);
|
|
110
|
+
|
|
87
111
|
var repoRoot = null;
|
|
88
112
|
var dir = path.resolve(envioDir);
|
|
89
113
|
for (var i = 0; i < 10; i++) {
|
package/src/Core.res.mjs
CHANGED
|
@@ -26,6 +26,10 @@ let loadDevAddon = (function(req, envioDir) {
|
|
|
26
26
|
var path = Nodepath;
|
|
27
27
|
var fs = Nodefs;
|
|
28
28
|
|
|
29
|
+
// Vitest test.env points workers at the addon globalSetup already built.
|
|
30
|
+
var preBuilt = process.env.ENVIO_DEV_ADDON;
|
|
31
|
+
if (preBuilt && fs.existsSync(preBuilt)) return req(preBuilt);
|
|
32
|
+
|
|
29
33
|
var repoRoot = null;
|
|
30
34
|
var dir = path.resolve(envioDir);
|
|
31
35
|
for (var i = 0; i < 10; i++) {
|
package/src/Ecosystem.res
CHANGED
|
@@ -10,6 +10,11 @@ type t = {
|
|
|
10
10
|
for SVM. Centralised here so adding a new ecosystem only requires a
|
|
11
11
|
new ecosystem record, not another switch in `Main.res`. */
|
|
12
12
|
onBlockMethodName: string,
|
|
13
|
+
/** What the ecosystem calls a contract and one of its events, for
|
|
14
|
+
registration errors that are shared across ecosystems — "contract" /
|
|
15
|
+
"event" on EVM and Fuel, "program" / "instruction" on SVM. */
|
|
16
|
+
contractNoun: string,
|
|
17
|
+
eventNoun: string,
|
|
13
18
|
/** Schema that unwraps the ecosystem-specific outer wrapper around the
|
|
14
19
|
user's `where`-returned filter (`block.number` on EVM, `block.height`
|
|
15
20
|
on Fuel, `slot` on SVM) and surfaces the raw inner `{_gte?, _lte?,
|
|
@@ -23,8 +28,8 @@ type t = {
|
|
|
23
28
|
`option<unknown>`. Separate from `onBlockFilterSchema` because event
|
|
24
29
|
block filters support only `_gte` (→ per-event `startBlock`) — `_lte`
|
|
25
30
|
and `_every` are rejected by the inner `eventBlockRangeSchema` in
|
|
26
|
-
`LogSelection.res`. SVM
|
|
27
|
-
schema always surfaces `None`. */
|
|
31
|
+
`LogSelection.res`. SVM parses its own `where` (including the
|
|
32
|
+
`block.slot` chunk), so its schema always surfaces `None`. */
|
|
28
33
|
onEventBlockFilterSchema: S.t<option<unknown>>,
|
|
29
34
|
/** Base logger injected at construction. Used to build per-item child
|
|
30
35
|
loggers (see `getItemLogger`). */
|
package/src/Env.res
CHANGED
|
@@ -163,6 +163,128 @@ module ClickHouse = {
|
|
|
163
163
|
let databaseEngine = () => read("ENVIO_CLICKHOUSE_DATABASE_ENGINE")
|
|
164
164
|
}
|
|
165
165
|
|
|
166
|
+
// The resolver process runs on its own, so its database connection comes from
|
|
167
|
+
// the same ENVIO_PG_* vars the indexer's does -- point them wherever reads
|
|
168
|
+
// should go. Only what genuinely differs from the indexer's shape gets a var
|
|
169
|
+
// of its own.
|
|
170
|
+
//
|
|
171
|
+
// Read at call time rather than module load, for the same reason ClickHouse's
|
|
172
|
+
// are: `envio dev` injects them after this module has been evaluated.
|
|
173
|
+
module Resolvers = {
|
|
174
|
+
%%private(
|
|
175
|
+
let read: string => option<string> = %raw(`(k) => {
|
|
176
|
+
const v = process.env[k];
|
|
177
|
+
return v === undefined || v === "" ? undefined : v;
|
|
178
|
+
}`)
|
|
179
|
+
|
|
180
|
+
// `Int.fromString` is `parseInt`: it takes the leading digits and drops the
|
|
181
|
+
// rest, so "60s" reads as 60 and "1e3" as 1. For a duration that is a
|
|
182
|
+
// thousandfold error in the quiet direction -- "60s" meaning a minute
|
|
183
|
+
// becomes a 60ms loop -- so the parse only accepts what it can write back
|
|
184
|
+
// unchanged.
|
|
185
|
+
let parseWhole = raw => {
|
|
186
|
+
let trimmed = raw->String.trim
|
|
187
|
+
switch trimmed->Int.fromString {
|
|
188
|
+
| Some(value) if trimmed === value->Int.toString => Some(value)
|
|
189
|
+
| _ => None
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
let readInt = (key, ~fallback) =>
|
|
194
|
+
switch read(key) {
|
|
195
|
+
| None => fallback
|
|
196
|
+
| Some(raw) =>
|
|
197
|
+
switch raw->parseWhole {
|
|
198
|
+
| Some(value) if value > 0 => value
|
|
199
|
+
| _ =>
|
|
200
|
+
JsError.throwWithMessage(
|
|
201
|
+
`Invalid ${key} value: "${raw}". Expected a positive whole number.`,
|
|
202
|
+
)
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
let port = () =>
|
|
208
|
+
switch readInt("ENVIO_RESOLVERS_PORT", ~fallback=9900) {
|
|
209
|
+
| value if value <= 65535 => value
|
|
210
|
+
| value =>
|
|
211
|
+
// `readInt` only rejects zero and below, so an out-of-range port reached
|
|
212
|
+
// `listen` and failed there instead, naming neither the variable nor why.
|
|
213
|
+
JsError.throwWithMessage(
|
|
214
|
+
`Invalid ENVIO_RESOLVERS_PORT value: "${value->Int.toString}". A TCP port is 1 to 65535.`,
|
|
215
|
+
)
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Sized as concurrent heavy requests x per-request fan-out, not as the
|
|
219
|
+
// indexer's two long-lived connections: one resolver request can hold four
|
|
220
|
+
// at once.
|
|
221
|
+
let poolSize = () => readInt("ENVIO_RESOLVERS_POOL_SIZE", ~fallback=25)
|
|
222
|
+
|
|
223
|
+
let poolWaitTimeoutMs = () => readInt("ENVIO_RESOLVERS_POOL_WAIT_TIMEOUT_MS", ~fallback=10_000)
|
|
224
|
+
|
|
225
|
+
// `envio dev` sets this on the resolver process it spawns: locally the
|
|
226
|
+
// caller is the person who wrote the resolver, so an unexpected error's own
|
|
227
|
+
// message is what they need. Deployed, the caller may be the public internet
|
|
228
|
+
// and the message can carry a connection string.
|
|
229
|
+
let exposeErrors = () =>
|
|
230
|
+
switch read("ENVIO_RESOLVERS_EXPOSE_ERRORS") {
|
|
231
|
+
| None
|
|
232
|
+
| Some("false") => false
|
|
233
|
+
| Some("true") => true
|
|
234
|
+
| Some(other) =>
|
|
235
|
+
// Refused rather than read as false: a typo here silently keeps a
|
|
236
|
+
// resolver's own error messages off the wire, which is the opposite of
|
|
237
|
+
// what the person who set it wanted, and nothing would say so.
|
|
238
|
+
JsError.throwWithMessage(
|
|
239
|
+
`Invalid ENVIO_RESOLVERS_EXPOSE_ERRORS value: "${other}". Expected "true" or "false".`,
|
|
240
|
+
)
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// The URL *Hasura* posts to, which is not the address this process binds:
|
|
244
|
+
// it is baked into every action, so Hasura has no other way to reach the
|
|
245
|
+
// resolvers.
|
|
246
|
+
let publicUrl = () => read("ENVIO_RESOLVERS_PUBLIC_URL")
|
|
247
|
+
|
|
248
|
+
// How often the metadata is re-asserted. 0 disables it, for a deployment
|
|
249
|
+
// where something else owns Hasura's metadata.
|
|
250
|
+
let metadataIntervalMs = () =>
|
|
251
|
+
switch read("ENVIO_RESOLVERS_METADATA_INTERVAL_MS") {
|
|
252
|
+
| None => 60_000
|
|
253
|
+
| Some(raw) =>
|
|
254
|
+
switch raw->parseWhole {
|
|
255
|
+
| Some(value) if value >= 0 => value
|
|
256
|
+
| _ =>
|
|
257
|
+
JsError.throwWithMessage(
|
|
258
|
+
`Invalid ENVIO_RESOLVERS_METADATA_INTERVAL_MS value: "${raw}". Expected a whole number of milliseconds, or 0 to disable.`,
|
|
259
|
+
)
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Required on every request to either route. Unset, the service cannot tell
|
|
264
|
+
// its callers apart from anything else that reaches the socket, and both
|
|
265
|
+
// routes take the role from the request body -- so anyone can claim `admin`.
|
|
266
|
+
let actionSecret = () => read("ENVIO_RESOLVERS_ACTION_SECRET")
|
|
267
|
+
|
|
268
|
+
// Read raw rather than through `Env.Hasura`, whose dev fallbacks would have
|
|
269
|
+
// this process apply metadata to a localhost Hasura nobody asked for. Only
|
|
270
|
+
// an endpoint someone set means "there is a Hasura to register with".
|
|
271
|
+
let hasuraEndpoint = () => read("HASURA_GRAPHQL_ENDPOINT")
|
|
272
|
+
let hasuraAdminSecret = () => read("HASURA_GRAPHQL_ADMIN_SECRET")
|
|
273
|
+
|
|
274
|
+
// Set where a transaction-mode pooler is the only way in. Those reject
|
|
275
|
+
// named prepared statements, so this gives up plan reuse.
|
|
276
|
+
let poolerBacked = () =>
|
|
277
|
+
switch read("ENVIO_RESOLVERS_POOLER_BACKED") {
|
|
278
|
+
| None
|
|
279
|
+
| Some("false") => false
|
|
280
|
+
| Some("true") => true
|
|
281
|
+
| Some(other) =>
|
|
282
|
+
JsError.throwWithMessage(
|
|
283
|
+
`Invalid ENVIO_RESOLVERS_POOLER_BACKED value: "${other}". Expected "true" or "false".`,
|
|
284
|
+
)
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
166
288
|
module Hasura = {
|
|
167
289
|
// Disable it on HS indexer run, since we don't have Hasura credentials anyways
|
|
168
290
|
// Also, it might be useful for some users who don't care about Hasura
|
|
@@ -180,7 +302,12 @@ module Hasura = {
|
|
|
180
302
|
~devFallback="http://localhost:8080/v1/metadata",
|
|
181
303
|
)
|
|
182
304
|
|
|
183
|
-
|
|
305
|
+
// Lazy on purpose. `graphqlEndpoint` is required in production, and a missing
|
|
306
|
+
// one arrives here as undefined -- slicing it threw a bare TypeError at import
|
|
307
|
+
// and killed the process before `EnvSafe.close` could say which variable was
|
|
308
|
+
// missing. Only the dev TUI reads this, so there is nothing to compute up
|
|
309
|
+
// front.
|
|
310
|
+
let url = () => graphqlEndpoint->String.slice(~start=0, ~end=-("/v1/metadata"->String.length))
|
|
184
311
|
|
|
185
312
|
let role = envSafe->EnvSafe.get("HASURA_GRAPHQL_ROLE", S.string, ~devFallback="admin")
|
|
186
313
|
|
package/src/Env.res.mjs
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import * as EnvSafe from "./EnvSafe.res.mjs";
|
|
4
4
|
import * as Logging from "./Logging.res.mjs";
|
|
5
5
|
import * as Postgres from "./bindings/Postgres.res.mjs";
|
|
6
|
+
import * as Stdlib_Int from "@rescript/runtime/lib/es6/Stdlib_Int.js";
|
|
6
7
|
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
7
8
|
import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
|
|
8
9
|
import * as HyperSyncClient from "./sources/HyperSyncClient.res.mjs";
|
|
@@ -147,6 +148,124 @@ let ClickHouse = {
|
|
|
147
148
|
databaseEngine: databaseEngine
|
|
148
149
|
};
|
|
149
150
|
|
|
151
|
+
let read$1 = ((k) => {
|
|
152
|
+
const v = process.env[k];
|
|
153
|
+
return v === undefined || v === "" ? undefined : v;
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
function parseWhole(raw) {
|
|
157
|
+
let trimmed = raw.trim();
|
|
158
|
+
let value = Stdlib_Int.fromString(trimmed, undefined);
|
|
159
|
+
if (value !== undefined && trimmed === value.toString()) {
|
|
160
|
+
return value;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function readInt(key, fallback) {
|
|
165
|
+
let raw = read$1(key);
|
|
166
|
+
if (raw === undefined) {
|
|
167
|
+
return fallback;
|
|
168
|
+
}
|
|
169
|
+
let value = parseWhole(raw);
|
|
170
|
+
if (value !== undefined && value > 0) {
|
|
171
|
+
return value;
|
|
172
|
+
}
|
|
173
|
+
return Stdlib_JsError.throwWithMessage(`Invalid ` + key + ` value: "` + raw + `". Expected a positive whole number.`);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function port$1() {
|
|
177
|
+
let value = readInt("ENVIO_RESOLVERS_PORT", 9900);
|
|
178
|
+
if (value <= 65535) {
|
|
179
|
+
return value;
|
|
180
|
+
} else {
|
|
181
|
+
return Stdlib_JsError.throwWithMessage(`Invalid ENVIO_RESOLVERS_PORT value: "` + value.toString() + `". A TCP port is 1 to 65535.`);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function poolSize() {
|
|
186
|
+
return readInt("ENVIO_RESOLVERS_POOL_SIZE", 25);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function poolWaitTimeoutMs() {
|
|
190
|
+
return readInt("ENVIO_RESOLVERS_POOL_WAIT_TIMEOUT_MS", 10000);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function exposeErrors() {
|
|
194
|
+
let other = read$1("ENVIO_RESOLVERS_EXPOSE_ERRORS");
|
|
195
|
+
if (other === undefined) {
|
|
196
|
+
return false;
|
|
197
|
+
}
|
|
198
|
+
switch (other) {
|
|
199
|
+
case "false" :
|
|
200
|
+
return false;
|
|
201
|
+
case "true" :
|
|
202
|
+
return true;
|
|
203
|
+
default:
|
|
204
|
+
return Stdlib_JsError.throwWithMessage(`Invalid ENVIO_RESOLVERS_EXPOSE_ERRORS value: "` + other + `". Expected "true" or "false".`);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function publicUrl() {
|
|
209
|
+
return read$1("ENVIO_RESOLVERS_PUBLIC_URL");
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function metadataIntervalMs() {
|
|
213
|
+
let raw = read$1("ENVIO_RESOLVERS_METADATA_INTERVAL_MS");
|
|
214
|
+
if (raw === undefined) {
|
|
215
|
+
return 60000;
|
|
216
|
+
}
|
|
217
|
+
let value = parseWhole(raw);
|
|
218
|
+
if (value !== undefined) {
|
|
219
|
+
if (value >= 0) {
|
|
220
|
+
return value;
|
|
221
|
+
} else {
|
|
222
|
+
return Stdlib_JsError.throwWithMessage(`Invalid ENVIO_RESOLVERS_METADATA_INTERVAL_MS value: "` + raw + `". Expected a whole number of milliseconds, or 0 to disable.`);
|
|
223
|
+
}
|
|
224
|
+
} else {
|
|
225
|
+
return Stdlib_JsError.throwWithMessage(`Invalid ENVIO_RESOLVERS_METADATA_INTERVAL_MS value: "` + raw + `". Expected a whole number of milliseconds, or 0 to disable.`);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function actionSecret() {
|
|
230
|
+
return read$1("ENVIO_RESOLVERS_ACTION_SECRET");
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function hasuraEndpoint() {
|
|
234
|
+
return read$1("HASURA_GRAPHQL_ENDPOINT");
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function hasuraAdminSecret() {
|
|
238
|
+
return read$1("HASURA_GRAPHQL_ADMIN_SECRET");
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function poolerBacked() {
|
|
242
|
+
let other = read$1("ENVIO_RESOLVERS_POOLER_BACKED");
|
|
243
|
+
if (other === undefined) {
|
|
244
|
+
return false;
|
|
245
|
+
}
|
|
246
|
+
switch (other) {
|
|
247
|
+
case "false" :
|
|
248
|
+
return false;
|
|
249
|
+
case "true" :
|
|
250
|
+
return true;
|
|
251
|
+
default:
|
|
252
|
+
return Stdlib_JsError.throwWithMessage(`Invalid ENVIO_RESOLVERS_POOLER_BACKED value: "` + other + `". Expected "true" or "false".`);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
let Resolvers = {
|
|
257
|
+
port: port$1,
|
|
258
|
+
poolSize: poolSize,
|
|
259
|
+
poolWaitTimeoutMs: poolWaitTimeoutMs,
|
|
260
|
+
exposeErrors: exposeErrors,
|
|
261
|
+
publicUrl: publicUrl,
|
|
262
|
+
metadataIntervalMs: metadataIntervalMs,
|
|
263
|
+
actionSecret: actionSecret,
|
|
264
|
+
hasuraEndpoint: hasuraEndpoint,
|
|
265
|
+
hasuraAdminSecret: hasuraAdminSecret,
|
|
266
|
+
poolerBacked: poolerBacked
|
|
267
|
+
};
|
|
268
|
+
|
|
150
269
|
let enabled = EnvSafe.get(envSafe, "ENVIO_HASURA", S$RescriptSchema.bool, undefined, true, undefined, undefined);
|
|
151
270
|
|
|
152
271
|
let s = EnvSafe.get(envSafe, "ENVIO_HASURA_RESPONSE_LIMIT", S$RescriptSchema.option(S$RescriptSchema.int), undefined, undefined, undefined, undefined);
|
|
@@ -155,7 +274,9 @@ let responseLimit = s !== undefined ? s : EnvSafe.get(envSafe, "HASURA_RESPONSE_
|
|
|
155
274
|
|
|
156
275
|
let graphqlEndpoint = EnvSafe.get(envSafe, "HASURA_GRAPHQL_ENDPOINT", S$RescriptSchema.string, undefined, undefined, "http://localhost:8080/v1/metadata", undefined);
|
|
157
276
|
|
|
158
|
-
|
|
277
|
+
function url() {
|
|
278
|
+
return graphqlEndpoint.slice(0, -"/v1/metadata".length | 0);
|
|
279
|
+
}
|
|
159
280
|
|
|
160
281
|
let role = EnvSafe.get(envSafe, "HASURA_GRAPHQL_ROLE", S$RescriptSchema.string, undefined, undefined, "admin", undefined);
|
|
161
282
|
|
|
@@ -240,6 +361,7 @@ export {
|
|
|
240
361
|
logStrategy,
|
|
241
362
|
Db,
|
|
242
363
|
ClickHouse,
|
|
364
|
+
Resolvers,
|
|
243
365
|
Hasura,
|
|
244
366
|
Configurable,
|
|
245
367
|
ThrottleWrites,
|