envio 3.14.0 → 3.14.1-resolvers.2
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/evm.schema.json +8 -1
- package/fuel.schema.json +7 -0
- package/index.d.ts +258 -5
- package/index.js +10 -0
- package/package.json +6 -6
- package/src/Bin.res +48 -0
- package/src/Bin.res.mjs +34 -1
- package/src/Config.res +12 -2
- package/src/Config.res.mjs +7 -0
- package/src/Env.res +141 -1
- package/src/Env.res.mjs +133 -1
- package/src/HandlerLoader.res +7 -13
- package/src/HandlerLoader.res.mjs +7 -14
- package/src/HandlerTypeCheck.res +207 -0
- package/src/HandlerTypeCheck.res.mjs +262 -0
- package/src/bindings/NodeJs.res +31 -0
- package/src/bindings/NodeJs.res.mjs +3 -0
- package/src/resolvers/ResolverProcess.res +805 -0
- package/src/resolvers/ResolverProcess.res.mjs +662 -0
- package/src/resolvers/ResolverQuery.res +313 -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 +259 -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 +333 -0
- package/src/resolvers/hasuraMetadata.js +137 -0
- package/src/resolvers/index.js +123 -0
- package/src/resolvers/manifest.js +376 -0
- package/src/resolvers/server.js +304 -0
- package/src/sources/EvmRpcClient.res +2 -0
- package/src/sources/RpcSource.res +17 -4
- package/src/sources/RpcSource.res.mjs +23 -5
- package/src/sources/SourceManager.res +63 -40
- package/src/sources/SourceManager.res.mjs +32 -20
- package/src/tui/Tui.res +1 -1
- package/src/tui/Tui.res.mjs +1 -1
- package/svm.schema.json +7 -0
- package/src/HandlerTypeCheck.mjs +0 -14
- package/src/HandlerTypeCheckWorker.mjs +0 -68
|
@@ -0,0 +1,662 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as Fs from "fs";
|
|
4
|
+
import * as Env from "../Env.res.mjs";
|
|
5
|
+
import * as Url from "url";
|
|
6
|
+
import * as Path from "path";
|
|
7
|
+
import * as Utils from "../Utils.res.mjs";
|
|
8
|
+
import * as Config from "../Config.res.mjs";
|
|
9
|
+
import * as DbJs from "./db.js";
|
|
10
|
+
import * as Logging from "../Logging.res.mjs";
|
|
11
|
+
import * as Nodefs from "node:fs";
|
|
12
|
+
import * as Process from "process";
|
|
13
|
+
import * as Nodepath from "node:path";
|
|
14
|
+
import * as IndexJs from "./index.js";
|
|
15
|
+
import * as ServerJs from "./server.js";
|
|
16
|
+
import * as Stdlib_JsExn from "@rescript/runtime/lib/es6/Stdlib_JsExn.js";
|
|
17
|
+
import * as HandlerLoader from "../HandlerLoader.res.mjs";
|
|
18
|
+
import * as InternalTable from "../db/InternalTable.res.mjs";
|
|
19
|
+
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
20
|
+
import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
|
|
21
|
+
import * as Stdlib_Promise from "@rescript/runtime/lib/es6/Stdlib_Promise.js";
|
|
22
|
+
import * as CollisionsJs from "./collisions.js";
|
|
23
|
+
import * as HasuraApplyJs from "./hasuraApply.js";
|
|
24
|
+
import * as Primitive_object from "@rescript/runtime/lib/es6/Primitive_object.js";
|
|
25
|
+
import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
|
|
26
|
+
import * as Primitive_string from "@rescript/runtime/lib/es6/Primitive_string.js";
|
|
27
|
+
import * as Promises from "node:fs/promises";
|
|
28
|
+
import * as Nodechild_process from "node:child_process";
|
|
29
|
+
import * as HasuraMetadataJs from "./hasuraMetadata.js";
|
|
30
|
+
import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
|
|
31
|
+
|
|
32
|
+
function buildRegisteredManifest(prim) {
|
|
33
|
+
return IndexJs.buildRegisteredManifest();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function getRegisteredResolvers(prim) {
|
|
37
|
+
return IndexJs.getRegisteredResolvers();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function buildHasuraMetadata(prim0, prim1) {
|
|
41
|
+
return HasuraMetadataJs.buildHasuraMetadata(prim0, prim1);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function applyResolverMetadata(prim) {
|
|
45
|
+
return HasuraApplyJs.applyResolverMetadata(prim);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function startMetadataReassert(prim) {
|
|
49
|
+
return HasuraApplyJs.startMetadataReassert(prim);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function checkCollisions(prim0, prim1) {
|
|
53
|
+
CollisionsJs.checkCollisions(prim0, prim1);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function schemaNamesOf(config) {
|
|
57
|
+
return {
|
|
58
|
+
entities: Config.getPgUserEntities(config).map(entity => entity.name),
|
|
59
|
+
enums: config.allEnums.map(enumConfig => enumConfig.name)
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function createResolverPoolFromEnv(prim) {
|
|
64
|
+
return DbJs.createResolverPoolFromEnv(prim);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function startResolverServer(prim) {
|
|
68
|
+
return ServerJs.startResolverServer(prim);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
let readyzProbeMs = ServerJs.READYZ_PROBE_MS;
|
|
72
|
+
|
|
73
|
+
function toImportUrl(projectRoot, relativePath) {
|
|
74
|
+
return Url.pathToFileURL(Path.resolve(projectRoot, relativePath)).toString();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async function sourceFilesIn(absoluteDir, relativePath) {
|
|
78
|
+
let files;
|
|
79
|
+
try {
|
|
80
|
+
let iterator = Promises.glob("**/*.{js,mjs,ts}", {
|
|
81
|
+
cwd: absoluteDir
|
|
82
|
+
});
|
|
83
|
+
files = await Utils.$$Array.fromAsyncIterator(iterator);
|
|
84
|
+
} catch (raw_exn) {
|
|
85
|
+
let exn = Primitive_exceptions.internalToException(raw_exn);
|
|
86
|
+
files = Stdlib_JsError.throwWithMessage(`Failed to read the resolvers directory '` + relativePath + `'. Node 22 or newer is required for directory auto-loading. Cause: ` + Utils.prettifyExn(exn));
|
|
87
|
+
}
|
|
88
|
+
return files.filter(file => !(file.includes(".test.") || file.includes(".spec.") || file.includes("_test.") || file.endsWith(".d.ts"))).toSorted(Primitive_string.compare);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async function loadOrThrow(projectRoot, relativePath) {
|
|
92
|
+
let absolute = Path.resolve(projectRoot, relativePath);
|
|
93
|
+
let isDir;
|
|
94
|
+
let exit = 0;
|
|
95
|
+
let stats;
|
|
96
|
+
try {
|
|
97
|
+
stats = Nodefs.statSync(absolute);
|
|
98
|
+
exit = 1;
|
|
99
|
+
} catch (exn) {
|
|
100
|
+
isDir = false;
|
|
101
|
+
}
|
|
102
|
+
if (exit === 1) {
|
|
103
|
+
isDir = stats.isDirectory();
|
|
104
|
+
}
|
|
105
|
+
let relativePaths;
|
|
106
|
+
if (isDir) {
|
|
107
|
+
let files = await sourceFilesIn(absolute, relativePath);
|
|
108
|
+
if (Utils.$$Array.isEmpty(files)) {
|
|
109
|
+
Logging.warn(`'` + relativePath + `' is a directory with no .js, .mjs or .ts files in it, so no resolvers were loaded.`);
|
|
110
|
+
}
|
|
111
|
+
relativePaths = files.map(file => relativePath + `/` + file);
|
|
112
|
+
} else {
|
|
113
|
+
relativePaths = [relativePath];
|
|
114
|
+
}
|
|
115
|
+
HandlerLoader.registerTsHooks();
|
|
116
|
+
for (let index = 0, index_finish = relativePaths.length; index < index_finish; ++index) {
|
|
117
|
+
let path = relativePaths[index];
|
|
118
|
+
try {
|
|
119
|
+
await import(toImportUrl(projectRoot, path));
|
|
120
|
+
} catch (raw_exn) {
|
|
121
|
+
let exn$1 = Primitive_exceptions.internalToException(raw_exn);
|
|
122
|
+
let e = Primitive_exceptions.internalToException(exn$1);
|
|
123
|
+
let cause = e.RE_EXN_ID === "JsExn" ? Stdlib_Option.getOr(Stdlib_JsExn.message(e._1), "no message") : "unknown error";
|
|
124
|
+
Stdlib_JsError.throwWithMessage(`Failed to load the resolvers module '` + path + `' named by \`resolvers:\` in config.yaml. Cause: ` + cause);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
let emptyManifest = { schemaVersion: 1, resolvers: [], types: [] };
|
|
130
|
+
|
|
131
|
+
async function writeManifest(config, projectRoot) {
|
|
132
|
+
let relativePath = config.resolvers;
|
|
133
|
+
let match;
|
|
134
|
+
if (relativePath !== undefined) {
|
|
135
|
+
await loadOrThrow(projectRoot, relativePath);
|
|
136
|
+
let bundle = IndexJs.buildRegisteredManifest();
|
|
137
|
+
let prim1 = schemaNamesOf(config);
|
|
138
|
+
CollisionsJs.checkCollisions(bundle.manifest, prim1);
|
|
139
|
+
match = bundle;
|
|
140
|
+
} else {
|
|
141
|
+
match = {
|
|
142
|
+
manifest: emptyManifest,
|
|
143
|
+
sdl: ""
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
let dir = Path.resolve(projectRoot, ".envio");
|
|
147
|
+
await Fs.promises.mkdir(dir, {
|
|
148
|
+
recursive: true
|
|
149
|
+
});
|
|
150
|
+
await Fs.promises.writeFile(Path.join(dir, "resolvers.json"), JSON.stringify(match.manifest, undefined, 2) + "\n");
|
|
151
|
+
return await Fs.promises.writeFile(Path.join(dir, "resolvers.graphql"), match.sdl);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function hasuraTarget() {
|
|
155
|
+
let match = Env.Resolvers.hasuraEndpoint();
|
|
156
|
+
let match$1 = Env.Resolvers.hasuraAdminSecret();
|
|
157
|
+
if (match !== undefined) {
|
|
158
|
+
if (match$1 !== undefined) {
|
|
159
|
+
return {
|
|
160
|
+
TAG: "Ok",
|
|
161
|
+
_0: [
|
|
162
|
+
match,
|
|
163
|
+
match$1
|
|
164
|
+
]
|
|
165
|
+
};
|
|
166
|
+
} else {
|
|
167
|
+
return {
|
|
168
|
+
TAG: "Error",
|
|
169
|
+
_0: "HASURA_GRAPHQL_ADMIN_SECRET is not set. Writing metadata is an admin operation, so Hasura will refuse without it."
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
} else {
|
|
173
|
+
return {
|
|
174
|
+
TAG: "Error",
|
|
175
|
+
_0: "HASURA_GRAPHQL_ENDPOINT is not set, so there is no Hasura to update. It is the full metadata URL, ending in /v1/metadata."
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function handlerUrlOrThrow() {
|
|
181
|
+
let url = Env.Resolvers.publicUrl();
|
|
182
|
+
if (url !== undefined) {
|
|
183
|
+
return url;
|
|
184
|
+
} else {
|
|
185
|
+
return Stdlib_JsError.throwWithMessage("ENVIO_RESOLVERS_PUBLIC_URL is not set. It is the URL Hasura posts to, which is not the address the resolver process binds -- Hasura has no other way to reach the resolvers, and it is baked into every action.");
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
async function metadataJson(config, projectRoot, handlerUrl) {
|
|
190
|
+
let relativePath = config.resolvers;
|
|
191
|
+
let manifest;
|
|
192
|
+
if (relativePath !== undefined) {
|
|
193
|
+
await loadOrThrow(projectRoot, relativePath);
|
|
194
|
+
let bundle = IndexJs.buildRegisteredManifest();
|
|
195
|
+
let prim1 = schemaNamesOf(config);
|
|
196
|
+
CollisionsJs.checkCollisions(bundle.manifest, prim1);
|
|
197
|
+
manifest = bundle.manifest;
|
|
198
|
+
} else {
|
|
199
|
+
manifest = emptyManifest;
|
|
200
|
+
}
|
|
201
|
+
return HasuraMetadataJs.buildHasuraMetadata(manifest, {
|
|
202
|
+
handlerUrl: handlerUrl,
|
|
203
|
+
actionSecret: Env.Resolvers.actionSecret()
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
async function migrate(config, projectRoot) {
|
|
208
|
+
let target = hasuraTarget();
|
|
209
|
+
let match;
|
|
210
|
+
match = target.TAG === "Ok" ? target._0 : Stdlib_JsError.throwWithMessage(target._0);
|
|
211
|
+
let handlerUrl = handlerUrlOrThrow();
|
|
212
|
+
let metadata = await metadataJson(config, projectRoot, handlerUrl);
|
|
213
|
+
return await HasuraApplyJs.applyResolverMetadata({
|
|
214
|
+
endpoint: match[0],
|
|
215
|
+
adminSecret: match[1],
|
|
216
|
+
metadata: metadata
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function cliEntry() {
|
|
221
|
+
return Path.resolve(Nodepath.dirname(Url.fileURLToPath(import.meta.url)), "..", "..", "bin.mjs");
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function compatibilityOf(pool, pgSchema, envioInfo) {
|
|
225
|
+
return async () => {
|
|
226
|
+
let sql = pool.forResolver({
|
|
227
|
+
name: "readyz",
|
|
228
|
+
timeoutMs: readyzProbeMs
|
|
229
|
+
}).sql;
|
|
230
|
+
let rows;
|
|
231
|
+
try {
|
|
232
|
+
rows = await sql.unsafe(`SELECT "config" FROM "` + pgSchema + `"."` + InternalTable.EnvioInfo.table.tableName + `" LIMIT 1;`, []);
|
|
233
|
+
} catch (raw_exn) {
|
|
234
|
+
let exn = Primitive_exceptions.internalToException(raw_exn);
|
|
235
|
+
if (InternalTable.isUndefinedTable(exn)) {
|
|
236
|
+
return "The database has no envio_info table, so it was not initialized by this indexer.";
|
|
237
|
+
}
|
|
238
|
+
throw exn;
|
|
239
|
+
}
|
|
240
|
+
let row = rows[0];
|
|
241
|
+
if (row === undefined) {
|
|
242
|
+
return "The database records no config, so it cannot be checked against this build. It was initialized by an envio version that predates the check.";
|
|
243
|
+
}
|
|
244
|
+
let changedPaths = Config.diffPaths(JSON.parse(Primitive_option.valFromOption(row).config), envioInfo);
|
|
245
|
+
if (changedPaths.length !== 0) {
|
|
246
|
+
return `The database was indexed with a different config: ` + changedPaths.join(", ") + `. This build must not answer for it.`;
|
|
247
|
+
} else {
|
|
248
|
+
return null;
|
|
249
|
+
}
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
async function serve(config, projectRoot, port, exposeErrors, pgSchemaOpt, envioInfoOpt) {
|
|
254
|
+
let pgSchema = pgSchemaOpt !== undefined ? pgSchemaOpt : Env.Db.publicSchema;
|
|
255
|
+
let envioInfo = envioInfoOpt !== undefined ? envioInfoOpt : Config.toEnvioInfo(Config.getPublicConfigJson());
|
|
256
|
+
let exposeErrors$1 = Stdlib_Option.getOr(exposeErrors, Env.Resolvers.exposeErrors());
|
|
257
|
+
let relativePath = config.resolvers;
|
|
258
|
+
let relativePath$1 = relativePath !== undefined ? relativePath : Stdlib_JsError.throwWithMessage("There is nothing to serve: config.yaml has no `resolvers:` entry. Add one naming the module that declares them, e.g. `resolvers: src/Resolvers.ts`.");
|
|
259
|
+
await loadOrThrow(projectRoot, relativePath$1);
|
|
260
|
+
let manifest = IndexJs.buildRegisteredManifest().manifest;
|
|
261
|
+
let prim1 = schemaNamesOf(config);
|
|
262
|
+
CollisionsJs.checkCollisions(manifest, prim1);
|
|
263
|
+
let resolvers = IndexJs.getRegisteredResolvers();
|
|
264
|
+
if (Utils.$$Array.isEmpty(resolvers)) {
|
|
265
|
+
Logging.warn(`'` + relativePath$1 + `' declared no resolvers, so this process will answer nothing. Every resolver has to be created with createResolver at module scope to register.`);
|
|
266
|
+
}
|
|
267
|
+
let entities = {};
|
|
268
|
+
Config.getPgUserEntities(config).forEach(entity => {
|
|
269
|
+
entities[entity.name] = entity;
|
|
270
|
+
});
|
|
271
|
+
let hasuraTargetOrNone = hasuraTarget();
|
|
272
|
+
if (hasuraTargetOrNone.TAG === "Ok") {
|
|
273
|
+
handlerUrlOrThrow();
|
|
274
|
+
Env.Resolvers.metadataIntervalMs();
|
|
275
|
+
}
|
|
276
|
+
let pool = DbJs.createResolverPoolFromEnv({
|
|
277
|
+
entities: entities,
|
|
278
|
+
pgSchema: pgSchema
|
|
279
|
+
});
|
|
280
|
+
let server;
|
|
281
|
+
try {
|
|
282
|
+
server = await ServerJs.startResolverServer({
|
|
283
|
+
resolvers: resolvers,
|
|
284
|
+
pool: pool,
|
|
285
|
+
port: port,
|
|
286
|
+
exposeErrors: exposeErrors$1,
|
|
287
|
+
actionSecret: Env.Resolvers.actionSecret(),
|
|
288
|
+
privateKeys: Env.Resolvers.privateKeys(),
|
|
289
|
+
checkCompatible: compatibilityOf(pool, pgSchema, envioInfo)
|
|
290
|
+
});
|
|
291
|
+
} catch (exn) {
|
|
292
|
+
try {
|
|
293
|
+
await pool.end();
|
|
294
|
+
} catch (exn$1) {
|
|
295
|
+
|
|
296
|
+
}
|
|
297
|
+
throw exn;
|
|
298
|
+
}
|
|
299
|
+
Logging.info(`Serving ` + resolvers.length.toString() + ` custom resolvers on port ` + server.port.toString());
|
|
300
|
+
let stopReassert;
|
|
301
|
+
if (hasuraTargetOrNone.TAG === "Ok") {
|
|
302
|
+
let match = hasuraTargetOrNone._0;
|
|
303
|
+
let adminSecret = match[1];
|
|
304
|
+
let endpoint = match[0];
|
|
305
|
+
let handlerUrl = handlerUrlOrThrow();
|
|
306
|
+
let metadata = HasuraMetadataJs.buildHasuraMetadata(manifest, {
|
|
307
|
+
handlerUrl: handlerUrl,
|
|
308
|
+
actionSecret: Env.Resolvers.actionSecret()
|
|
309
|
+
});
|
|
310
|
+
let intervalMs = Env.Resolvers.metadataIntervalMs();
|
|
311
|
+
let exit = 0;
|
|
312
|
+
let val;
|
|
313
|
+
try {
|
|
314
|
+
val = await HasuraApplyJs.applyResolverMetadata({
|
|
315
|
+
endpoint: endpoint,
|
|
316
|
+
adminSecret: adminSecret,
|
|
317
|
+
metadata: metadata
|
|
318
|
+
});
|
|
319
|
+
exit = 1;
|
|
320
|
+
} catch (raw_exn) {
|
|
321
|
+
let exn$2 = Primitive_exceptions.internalToException(raw_exn);
|
|
322
|
+
Logging.error(`Failed to register the custom resolvers with Hasura, so Hasura will not route to this process yet: ` + Utils.prettifyExn(exn$2) + `. ` + (
|
|
323
|
+
intervalMs === 0 ? "ENVIO_RESOLVERS_METADATA_INTERVAL_MS is 0, so this will not be retried." : `Retrying every ` + intervalMs.toString() + `ms.`
|
|
324
|
+
));
|
|
325
|
+
}
|
|
326
|
+
if (exit === 1) {
|
|
327
|
+
Logging.info(val.applied ? `Registered custom resolvers with Hasura: ` + val.reasons.join("; ") : "Hasura already publishes these custom resolvers; nothing to register");
|
|
328
|
+
}
|
|
329
|
+
stopReassert = intervalMs === 0 ? () => {} : HasuraApplyJs.startMetadataReassert({
|
|
330
|
+
endpoint: endpoint,
|
|
331
|
+
adminSecret: adminSecret,
|
|
332
|
+
metadata: metadata,
|
|
333
|
+
intervalMs: intervalMs,
|
|
334
|
+
onApplied: reasons => Logging.warn(`Hasura had lost the custom resolvers and they were restored: ` + reasons.join("; ")),
|
|
335
|
+
onError: exn => Logging.error(`Failed to re-assert the custom resolver metadata: ` + Utils.prettifyExn(exn))
|
|
336
|
+
});
|
|
337
|
+
} else {
|
|
338
|
+
Logging.info(hasuraTargetOrNone._0 + ` Nothing was registered with Hasura; this process still answers /hasura-action and /resolve.`);
|
|
339
|
+
stopReassert = () => {};
|
|
340
|
+
}
|
|
341
|
+
let stopped = {
|
|
342
|
+
contents: undefined
|
|
343
|
+
};
|
|
344
|
+
let shutdown = () => {
|
|
345
|
+
let promise = stopped.contents;
|
|
346
|
+
if (promise !== undefined) {
|
|
347
|
+
return promise;
|
|
348
|
+
}
|
|
349
|
+
let promise$1 = (async () => {
|
|
350
|
+
stopReassert();
|
|
351
|
+
try {
|
|
352
|
+
await Promise.race([
|
|
353
|
+
server.close(),
|
|
354
|
+
Utils.delay(5000)
|
|
355
|
+
]);
|
|
356
|
+
} catch (exn) {
|
|
357
|
+
|
|
358
|
+
}
|
|
359
|
+
try {
|
|
360
|
+
await pool.end();
|
|
361
|
+
return;
|
|
362
|
+
} catch (exn$1) {
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
})();
|
|
366
|
+
stopped.contents = promise$1;
|
|
367
|
+
return promise$1;
|
|
368
|
+
};
|
|
369
|
+
return {
|
|
370
|
+
server: server,
|
|
371
|
+
pool: pool,
|
|
372
|
+
shutdown: shutdown
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
function handleSignals(running) {
|
|
377
|
+
[
|
|
378
|
+
"SIGTERM",
|
|
379
|
+
"SIGINT"
|
|
380
|
+
].forEach(signal => {
|
|
381
|
+
process.on(signal, () => {
|
|
382
|
+
Logging.info(`Received ` + signal + `, draining custom resolvers...`);
|
|
383
|
+
(async () => {
|
|
384
|
+
await running.shutdown();
|
|
385
|
+
Process.exit(0);
|
|
386
|
+
})();
|
|
387
|
+
});
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
async function waitForHealthz(port) {
|
|
392
|
+
let deadline = Date.now() + 20000;
|
|
393
|
+
let attempt = async () => {
|
|
394
|
+
let answered;
|
|
395
|
+
let exit = 0;
|
|
396
|
+
let response;
|
|
397
|
+
try {
|
|
398
|
+
response = await fetch(`http://127.0.0.1:` + port.toString() + `/healthz`, {
|
|
399
|
+
method: "GET"
|
|
400
|
+
});
|
|
401
|
+
exit = 1;
|
|
402
|
+
} catch (exn) {
|
|
403
|
+
answered = false;
|
|
404
|
+
}
|
|
405
|
+
if (exit === 1) {
|
|
406
|
+
answered = response.ok;
|
|
407
|
+
}
|
|
408
|
+
if (answered) {
|
|
409
|
+
return true;
|
|
410
|
+
} else if (Date.now() > deadline) {
|
|
411
|
+
return false;
|
|
412
|
+
} else {
|
|
413
|
+
await Utils.delay(200);
|
|
414
|
+
return await attempt();
|
|
415
|
+
}
|
|
416
|
+
};
|
|
417
|
+
return await attempt();
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
function stopChild(child) {
|
|
421
|
+
return new Promise((resolve, param) => {
|
|
422
|
+
let match = child.pid;
|
|
423
|
+
if (match !== undefined) {
|
|
424
|
+
child.on("exit", (param, param$1) => resolve());
|
|
425
|
+
child.kill("SIGTERM");
|
|
426
|
+
(async () => {
|
|
427
|
+
await Utils.delay(5000);
|
|
428
|
+
return resolve();
|
|
429
|
+
})();
|
|
430
|
+
return;
|
|
431
|
+
} else {
|
|
432
|
+
return resolve();
|
|
433
|
+
}
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
async function startForDev(config, projectRoot) {
|
|
438
|
+
let match = config.resolvers;
|
|
439
|
+
if (match === undefined) {
|
|
440
|
+
return;
|
|
441
|
+
}
|
|
442
|
+
if (Primitive_object.equal(process.env["ENVIO_RESOLVERS_EXTERNAL"], "true")) {
|
|
443
|
+
Logging.info("ENVIO_RESOLVERS_EXTERNAL=true — not starting resolvers here. Run `envio resolvers` in another terminal, and envio-serve against .envio/serve-project.");
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
let configFile = Stdlib_Option.getOr(process.env["ENVIO_CONFIG"], "config.yaml");
|
|
447
|
+
let port = Env.Resolvers.port();
|
|
448
|
+
let childEnv = Object.assign({}, process.env);
|
|
449
|
+
childEnv["ENVIO_RESOLVERS_PORT"] = port.toString();
|
|
450
|
+
childEnv["ENVIO_RESOLVERS_EXPOSE_ERRORS"] = "true";
|
|
451
|
+
childEnv["ENVIO_CONFIG"] = configFile;
|
|
452
|
+
if (Env.Hasura.enabled) {
|
|
453
|
+
let endpoint = Env.Resolvers.hasuraEndpoint();
|
|
454
|
+
let endpoint$1;
|
|
455
|
+
if (endpoint !== undefined) {
|
|
456
|
+
endpoint$1 = endpoint;
|
|
457
|
+
} else {
|
|
458
|
+
let hasuraPort = Stdlib_Option.getOr(process.env["HASURA_EXTERNAL_PORT"], "8080");
|
|
459
|
+
endpoint$1 = `http://localhost:` + hasuraPort + `/v1/metadata`;
|
|
460
|
+
}
|
|
461
|
+
childEnv["HASURA_GRAPHQL_ENDPOINT"] = endpoint$1;
|
|
462
|
+
childEnv["HASURA_GRAPHQL_ADMIN_SECRET"] = Stdlib_Option.getOr(Env.Resolvers.hasuraAdminSecret(), "testing");
|
|
463
|
+
let match$1 = Env.Resolvers.publicUrl();
|
|
464
|
+
if (match$1 !== undefined) {
|
|
465
|
+
|
|
466
|
+
} else {
|
|
467
|
+
childEnv["ENVIO_RESOLVERS_PUBLIC_URL"] = `http://host.docker.internal:` + port.toString() + `/hasura-action`;
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
let resolverChild = Nodechild_process.spawn(process.execPath, [
|
|
471
|
+
cliEntry(),
|
|
472
|
+
"resolvers"
|
|
473
|
+
], {
|
|
474
|
+
env: childEnv,
|
|
475
|
+
stdio: "inherit",
|
|
476
|
+
detached: false,
|
|
477
|
+
cwd: projectRoot
|
|
478
|
+
});
|
|
479
|
+
resolverChild.on("exit", (code, signal) => {
|
|
480
|
+
if (!(code == null) && code === 0) {
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
483
|
+
if (signal == null) {
|
|
484
|
+
if (code == null) {
|
|
485
|
+
return Logging.info("Custom resolvers stopped.");
|
|
486
|
+
} else {
|
|
487
|
+
return Logging.error(`The custom resolvers process exited with code ` + code.toString() + `. The indexer keeps running; fix the resolver and run \`envio dev\` again.`);
|
|
488
|
+
}
|
|
489
|
+
} else {
|
|
490
|
+
return Logging.info(`Custom resolvers stopped (` + signal + `).`);
|
|
491
|
+
}
|
|
492
|
+
});
|
|
493
|
+
if (!await waitForHealthz(port)) {
|
|
494
|
+
Logging.warn(`The custom resolvers process did not answer /healthz on port ` + port.toString() + ` within ` + (20).toString() + `s. Its own output above says why.`);
|
|
495
|
+
}
|
|
496
|
+
await writeManifest(config, projectRoot);
|
|
497
|
+
let serveDir = Path.resolve(projectRoot, ".envio", "serve-project");
|
|
498
|
+
await Fs.promises.mkdir(serveDir, {
|
|
499
|
+
recursive: true
|
|
500
|
+
});
|
|
501
|
+
let envioDir = Path.resolve(projectRoot, ".envio");
|
|
502
|
+
let root = Path.resolve(projectRoot);
|
|
503
|
+
[
|
|
504
|
+
[
|
|
505
|
+
Path.resolve(root, configFile),
|
|
506
|
+
"config.yaml"
|
|
507
|
+
],
|
|
508
|
+
[
|
|
509
|
+
Path.resolve(root, "schema.graphql"),
|
|
510
|
+
"schema.graphql"
|
|
511
|
+
],
|
|
512
|
+
[
|
|
513
|
+
envioDir + `/resolvers.json`,
|
|
514
|
+
"resolvers.json"
|
|
515
|
+
]
|
|
516
|
+
].forEach(param => {
|
|
517
|
+
let from = param[0];
|
|
518
|
+
if (Nodefs.existsSync(from)) {
|
|
519
|
+
Nodefs.copyFileSync(from, serveDir + `/` + param[1]);
|
|
520
|
+
return;
|
|
521
|
+
}
|
|
522
|
+
});
|
|
523
|
+
let serveChild = {
|
|
524
|
+
contents: undefined
|
|
525
|
+
};
|
|
526
|
+
let bin = process.env["ENVIO_SERVE_BIN"];
|
|
527
|
+
let exit = 0;
|
|
528
|
+
if (bin !== undefined && Nodefs.existsSync(bin)) {
|
|
529
|
+
let servePort = Stdlib_Option.getOr(process.env["ENVIO_SERVE_PORT"], "8080");
|
|
530
|
+
let startServe = () => {
|
|
531
|
+
let serveEnv = Object.assign({}, process.env);
|
|
532
|
+
serveEnv["ENVIO_SERVE_RESOLVERS_URL"] = `http://127.0.0.1:` + port.toString();
|
|
533
|
+
let child = Nodechild_process.spawn(bin, [
|
|
534
|
+
"--directory",
|
|
535
|
+
serveDir,
|
|
536
|
+
"--config",
|
|
537
|
+
"config.yaml",
|
|
538
|
+
"--port",
|
|
539
|
+
servePort
|
|
540
|
+
], {
|
|
541
|
+
env: serveEnv,
|
|
542
|
+
stdio: "inherit",
|
|
543
|
+
detached: false
|
|
544
|
+
});
|
|
545
|
+
child.unref();
|
|
546
|
+
serveChild.contents = Primitive_option.some(child);
|
|
547
|
+
Logging.info(`Custom resolvers ready — GraphQL with custom fields on port ` + servePort);
|
|
548
|
+
};
|
|
549
|
+
let pool = DbJs.createResolverPoolFromEnv({
|
|
550
|
+
entities: {},
|
|
551
|
+
pgSchema: Env.Db.publicSchema
|
|
552
|
+
});
|
|
553
|
+
let sql = pool.forResolver({
|
|
554
|
+
name: "dev-wait",
|
|
555
|
+
timeoutMs: 5000
|
|
556
|
+
}).sql;
|
|
557
|
+
let waitForTables = async attempt => {
|
|
558
|
+
if (attempt >= 120) {
|
|
559
|
+
Logging.warn("Timed out waiting for the indexer's tables; starting envio-serve anyway.");
|
|
560
|
+
startServe();
|
|
561
|
+
return await pool.end();
|
|
562
|
+
}
|
|
563
|
+
let count;
|
|
564
|
+
let exit = 0;
|
|
565
|
+
let rows;
|
|
566
|
+
try {
|
|
567
|
+
rows = await sql.unsafe("SELECT count(*)::int AS n FROM information_schema.tables WHERE table_schema = $1;", [Env.Db.publicSchema]);
|
|
568
|
+
exit = 1;
|
|
569
|
+
} catch (exn) {
|
|
570
|
+
count = 0;
|
|
571
|
+
}
|
|
572
|
+
if (exit === 1) {
|
|
573
|
+
count = Stdlib_Option.mapOr(rows[0], 0, row => row.n);
|
|
574
|
+
}
|
|
575
|
+
if (count > 0) {
|
|
576
|
+
startServe();
|
|
577
|
+
return await pool.end();
|
|
578
|
+
} else {
|
|
579
|
+
await Utils.delay(1000);
|
|
580
|
+
return await waitForTables(attempt + 1 | 0);
|
|
581
|
+
}
|
|
582
|
+
};
|
|
583
|
+
Stdlib_Promise.$$catch(waitForTables(0), exn => {
|
|
584
|
+
Logging.warn(`Stopped waiting for the resolver tables: ` + Utils.prettifyExn(exn));
|
|
585
|
+
return Promise.resolve();
|
|
586
|
+
});
|
|
587
|
+
} else {
|
|
588
|
+
exit = 1;
|
|
589
|
+
}
|
|
590
|
+
if (exit === 1) {
|
|
591
|
+
if (Env.Hasura.enabled) {
|
|
592
|
+
Logging.info(`Custom resolvers are published by Hasura as actions. Set ENVIO_SERVE_BIN to serve them through envio-serve instead.`);
|
|
593
|
+
} else {
|
|
594
|
+
Logging.warn(`Resolvers are declared, but ENVIO_HASURA=false and ENVIO_SERVE_BIN is unset, so nothing publishes them. ` + (`Drop ENVIO_HASURA=false to have Hasura serve them as actions, or point ENVIO_SERVE_BIN at an envio-serve binary and run it against ` + serveDir + `.`));
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
let stop = async () => {
|
|
598
|
+
let child = serveChild.contents;
|
|
599
|
+
if (child !== undefined) {
|
|
600
|
+
await stopChild(Primitive_option.valFromOption(child));
|
|
601
|
+
}
|
|
602
|
+
return await stopChild(resolverChild);
|
|
603
|
+
};
|
|
604
|
+
let killChildren = () => {
|
|
605
|
+
let child = serveChild.contents;
|
|
606
|
+
if (child !== undefined) {
|
|
607
|
+
Primitive_option.valFromOption(child).kill("SIGTERM");
|
|
608
|
+
}
|
|
609
|
+
resolverChild.kill("SIGTERM");
|
|
610
|
+
};
|
|
611
|
+
process.on("exit", killChildren);
|
|
612
|
+
[
|
|
613
|
+
"SIGTERM",
|
|
614
|
+
"SIGINT"
|
|
615
|
+
].forEach(signal => {
|
|
616
|
+
process.on(signal, () => {
|
|
617
|
+
killChildren();
|
|
618
|
+
Process.exit(0);
|
|
619
|
+
});
|
|
620
|
+
});
|
|
621
|
+
return {
|
|
622
|
+
port: port,
|
|
623
|
+
pid: resolverChild.pid,
|
|
624
|
+
stop: stop
|
|
625
|
+
};
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
let closeGracePeriodMs = 5000;
|
|
629
|
+
|
|
630
|
+
let healthzTimeoutMs = 20000;
|
|
631
|
+
|
|
632
|
+
export {
|
|
633
|
+
buildRegisteredManifest,
|
|
634
|
+
getRegisteredResolvers,
|
|
635
|
+
buildHasuraMetadata,
|
|
636
|
+
applyResolverMetadata,
|
|
637
|
+
startMetadataReassert,
|
|
638
|
+
checkCollisions,
|
|
639
|
+
schemaNamesOf,
|
|
640
|
+
createResolverPoolFromEnv,
|
|
641
|
+
startResolverServer,
|
|
642
|
+
readyzProbeMs,
|
|
643
|
+
toImportUrl,
|
|
644
|
+
sourceFilesIn,
|
|
645
|
+
loadOrThrow,
|
|
646
|
+
emptyManifest,
|
|
647
|
+
writeManifest,
|
|
648
|
+
hasuraTarget,
|
|
649
|
+
handlerUrlOrThrow,
|
|
650
|
+
metadataJson,
|
|
651
|
+
migrate,
|
|
652
|
+
closeGracePeriodMs,
|
|
653
|
+
cliEntry,
|
|
654
|
+
compatibilityOf,
|
|
655
|
+
serve,
|
|
656
|
+
handleSignals,
|
|
657
|
+
healthzTimeoutMs,
|
|
658
|
+
waitForHealthz,
|
|
659
|
+
stopChild,
|
|
660
|
+
startForDev,
|
|
661
|
+
}
|
|
662
|
+
/* readyzProbeMs Not a pure module */
|