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.
Files changed (42) hide show
  1. package/evm.schema.json +8 -1
  2. package/fuel.schema.json +7 -0
  3. package/index.d.ts +258 -5
  4. package/index.js +10 -0
  5. package/package.json +6 -6
  6. package/src/Bin.res +48 -0
  7. package/src/Bin.res.mjs +34 -1
  8. package/src/Config.res +12 -2
  9. package/src/Config.res.mjs +7 -0
  10. package/src/Env.res +141 -1
  11. package/src/Env.res.mjs +133 -1
  12. package/src/HandlerLoader.res +7 -13
  13. package/src/HandlerLoader.res.mjs +7 -14
  14. package/src/HandlerTypeCheck.res +207 -0
  15. package/src/HandlerTypeCheck.res.mjs +262 -0
  16. package/src/bindings/NodeJs.res +31 -0
  17. package/src/bindings/NodeJs.res.mjs +3 -0
  18. package/src/resolvers/ResolverProcess.res +805 -0
  19. package/src/resolvers/ResolverProcess.res.mjs +662 -0
  20. package/src/resolvers/ResolverQuery.res +313 -0
  21. package/src/resolvers/ResolverQuery.res.mjs +220 -0
  22. package/src/resolvers/collisions.js +108 -0
  23. package/src/resolvers/db.js +363 -0
  24. package/src/resolvers/dispatch.js +259 -0
  25. package/src/resolvers/errors.js +30 -0
  26. package/src/resolvers/graphqlSelection.js +292 -0
  27. package/src/resolvers/hasuraAction.js +84 -0
  28. package/src/resolvers/hasuraApply.js +333 -0
  29. package/src/resolvers/hasuraMetadata.js +137 -0
  30. package/src/resolvers/index.js +123 -0
  31. package/src/resolvers/manifest.js +376 -0
  32. package/src/resolvers/server.js +304 -0
  33. package/src/sources/EvmRpcClient.res +2 -0
  34. package/src/sources/RpcSource.res +17 -4
  35. package/src/sources/RpcSource.res.mjs +23 -5
  36. package/src/sources/SourceManager.res +63 -40
  37. package/src/sources/SourceManager.res.mjs +32 -20
  38. package/src/tui/Tui.res +1 -1
  39. package/src/tui/Tui.res.mjs +1 -1
  40. package/svm.schema.json +7 -0
  41. package/src/HandlerTypeCheck.mjs +0 -14
  42. package/src/HandlerTypeCheckWorker.mjs +0 -68
@@ -0,0 +1,313 @@
1
+ // Written against the entity table definitions rather than raw rows, so the
2
+ // resolver path reads entities the way the indexer writes them: column names
3
+ // come from the table's own field mapping, filter values are serialized with
4
+ // each field's schema, and rows are decoded with `Table.pgRowsSchema`. A
5
+ // `db.find` therefore hands back what a handler's `context.<Entity>.get` does,
6
+ // BigInt and enum values included.
7
+ //
8
+ // The operator vocabulary is `getWhere`'s, so one filter syntax spans handlers
9
+ // and resolvers. The translation is deliberately not shared with
10
+ // `EntityFilter.parseGetWhereOrThrow`: that expands `_in` and `_gte` into a
11
+ // disjunction for the load layer to memoize per value, which here would turn
12
+ // one indexed query into several.
13
+
14
+ type orderBy = {field: string, direction: string}
15
+
16
+ type findOptions = {
17
+ entityConfig: Internal.entityConfig,
18
+ pgSchema: string,
19
+ where?: dict<unknown>,
20
+ orderBy?: array<orderBy>,
21
+ limit?: int,
22
+ offset?: int,
23
+ }
24
+
25
+ type query = {text: string, params: array<JSON.t>}
26
+
27
+ let quoted = (~pgSchema, ~tableName) => `"${pgSchema}"."${tableName}"`
28
+
29
+ let getQueryFieldOrThrow = (~table: Table.table, ~entityName, ~apiFieldName, ~where) =>
30
+ switch table->Table.queryFields->Dict.get(apiFieldName) {
31
+ | Some(queryField) => queryField
32
+ | None =>
33
+ JsError.throwWithMessage(
34
+ `Invalid field "${apiFieldName}" in db.find("${entityName}", { ${where}: ... }). The entity has no such field.`,
35
+ )
36
+ }
37
+
38
+ let bindOrThrow = (
39
+ ~queryField: Table.queryField,
40
+ ~entityName,
41
+ ~apiFieldName,
42
+ ~operator,
43
+ ~value: unknown,
44
+ ~isArray,
45
+ ~params: array<JSON.t>,
46
+ ) => {
47
+ let json = try value->S.reverseConvertToJsonOrThrow(
48
+ isArray ? queryField.arrayFieldSchema : queryField.fieldSchema,
49
+ ) catch {
50
+ | exn =>
51
+ let reason = switch exn {
52
+ | JsExn(e) => e->JsExn.message->Option.getOr("it isn't the field's type")
53
+ | _ => "it isn't the field's type"
54
+ }
55
+ JsError.throwWithMessage(
56
+ `Invalid value for db.find("${entityName}", { where: { ${apiFieldName}: { ${operator}: ... } } }): ${reason}`,
57
+ )
58
+ }
59
+ params->Array.push(json)->ignore
60
+ `$${params->Array.length->Int.toString}`
61
+ }
62
+
63
+ let makeWhereCondition = (~table, ~entityName, ~where: dict<unknown>, ~params) => {
64
+ let apiFieldNames = where->Dict.keysToArray
65
+ if apiFieldNames->Array.length === 0 {
66
+ JsError.throwWithMessage(
67
+ `Empty where passed to db.find("${entityName}"). Drop it, or filter like { fieldName: { _eq: value } }.`,
68
+ )
69
+ }
70
+ apiFieldNames
71
+ ->Array.flatMap(apiFieldName => {
72
+ let queryField = getQueryFieldOrThrow(~table, ~entityName, ~apiFieldName, ~where="where")
73
+ let operators = where->Dict.getUnsafe(apiFieldName)
74
+ if operators->typeof !== #object || operators->Array.isArray || operators === %raw(`null`) {
75
+ JsError.throwWithMessage(
76
+ `Invalid value in db.find("${entityName}", { where: { ${apiFieldName}: ... } }). Provide an operator like { _eq: value }.`,
77
+ )
78
+ }
79
+ let operators = operators->(Utils.magic: unknown => dict<unknown>)
80
+ let operatorKeys = operators->Dict.keysToArray
81
+ if operatorKeys->Array.length === 0 {
82
+ JsError.throwWithMessage(
83
+ `Empty operator in db.find("${entityName}", { where: { ${apiFieldName}: {} } }). Valid operators are _eq, _gt, _lt, _gte, _lte, _in.`,
84
+ )
85
+ }
86
+ operatorKeys->Array.map(operator => {
87
+ let value = operators->Dict.getUnsafe(operator)
88
+ let bind = (~isArray=false) =>
89
+ bindOrThrow(~queryField, ~entityName, ~apiFieldName, ~operator, ~value, ~isArray, ~params)
90
+ let column = `"${queryField.pgDbFieldName}"`
91
+ switch operator {
92
+ | "_eq" => `${column} = ${bind()}`
93
+ | "_gt" => `${column} > ${bind()}`
94
+ | "_lt" => `${column} < ${bind()}`
95
+ | "_gte" => `${column} >= ${bind()}`
96
+ | "_lte" => `${column} <= ${bind()}`
97
+ | "_in" =>
98
+ if !(value->Array.isArray) {
99
+ JsError.throwWithMessage(
100
+ `Invalid value in db.find("${entityName}", { where: { ${apiFieldName}: { _in: ... } } }). _in takes an array.`,
101
+ )
102
+ }
103
+ `${column} = ANY(${bind(~isArray=true)})`
104
+ | _ =>
105
+ JsError.throwWithMessage(
106
+ `Invalid operator "${operator}" in db.find("${entityName}", { where: { ${apiFieldName}: { ${operator}: ... } } }). Valid operators are _eq, _gt, _lt, _gte, _lte, _in.`,
107
+ )
108
+ }
109
+ })
110
+ })
111
+ ->Array.join(" AND ")
112
+ }
113
+
114
+ let makeOrderByClause = (~table, ~entityName, ~orderBy: array<orderBy>) =>
115
+ switch orderBy {
116
+ | [] => ""
117
+ | orderBy =>
118
+ " ORDER BY " ++
119
+ orderBy
120
+ ->Array.map(({field, direction}) => {
121
+ let queryField = getQueryFieldOrThrow(
122
+ ~table,
123
+ ~entityName,
124
+ ~apiFieldName=field,
125
+ ~where="orderBy",
126
+ )
127
+ let direction = switch direction->String.toLowerCase {
128
+ | "asc" => "ASC"
129
+ | "desc" => "DESC"
130
+ | other =>
131
+ JsError.throwWithMessage(
132
+ `Invalid direction "${other}" in db.find("${entityName}", { orderBy: [{ field: "${field}" }] }). Use "asc" or "desc".`,
133
+ )
134
+ }
135
+ `"${queryField.pgDbFieldName}" ${direction}`
136
+ })
137
+ ->Array.join(", ")
138
+ }
139
+
140
+ // Bound rather than spliced: a count reaching SQL as a parameter can't widen
141
+ // the statement no matter where the resolver got it from.
142
+ let makeBoundCount = (~keyword, ~entityName, ~count, ~params: array<JSON.t>) => {
143
+ if count < 0 || count->Int.toFloat->Math.trunc !== count->Int.toFloat {
144
+ JsError.throwWithMessage(
145
+ `Invalid ${keyword->String.toLowerCase} ${count->Int.toString} in db.find("${entityName}"). It must be a non-negative whole number.`,
146
+ )
147
+ }
148
+ params->Array.push(JSON.Encode.int(count))->ignore
149
+ ` ${keyword} $${params->Array.length->Int.toString}`
150
+ }
151
+
152
+ let makeFindQuery = (options: findOptions): query => {
153
+ let {entityConfig, pgSchema} = options
154
+ let table = entityConfig.table
155
+ let entityName = entityConfig.name
156
+ let params = []
157
+
158
+ let whereClause = switch options.where {
159
+ | None => ""
160
+ | Some(where) => " WHERE " ++ makeWhereCondition(~table, ~entityName, ~where, ~params)
161
+ }
162
+ let orderByClause = switch options.orderBy {
163
+ | None => ""
164
+ | Some(orderBy) => makeOrderByClause(~table, ~entityName, ~orderBy)
165
+ }
166
+ let limitClause = switch options.limit {
167
+ | None => ""
168
+ | Some(count) => makeBoundCount(~keyword="LIMIT", ~entityName, ~count, ~params)
169
+ }
170
+ let offsetClause = switch options.offset {
171
+ | None => ""
172
+ | Some(count) => makeBoundCount(~keyword="OFFSET", ~entityName, ~count, ~params)
173
+ }
174
+
175
+ {
176
+ text: `SELECT * FROM ${quoted(
177
+ ~pgSchema,
178
+ ~tableName=table.tableName,
179
+ )}${whereClause}${orderByClause}${limitClause}${offsetClause};`,
180
+ params,
181
+ }
182
+ }
183
+
184
+ // `id` alone identifies a row only on a cross-chain entity: a per-chain table
185
+ // is keyed by (id, chain id), so a bare `get` there would answer with whichever
186
+ // chain's row Postgres reached first.
187
+ let makeGetQuery = (entityConfig: Internal.entityConfig, pgSchema: string, id: unknown): query => {
188
+ let table = entityConfig.table
189
+ let entityName = entityConfig.name
190
+ switch table->Table.getChainIdField {
191
+ | Some(_) =>
192
+ JsError.throwWithMessage(
193
+ `db.get("${entityName}", id) is ambiguous: the entity is per-chain, so the same id can exist on every chain. Use db.find("${entityName}", { where: { id: { _eq: id }, ${Config.chainIdFieldName}: { _eq: chainId } } }).`,
194
+ )
195
+ | None => ()
196
+ }
197
+ // Bound through the id field's own schema, like every other value: the
198
+ // loaders must not be the one place a value reaches SQL unconverted.
199
+ let params = []
200
+ let queryField = getQueryFieldOrThrow(
201
+ ~table,
202
+ ~entityName,
203
+ ~apiFieldName=Table.idFieldName,
204
+ ~where="where",
205
+ )
206
+ let placeholder = bindOrThrow(
207
+ ~queryField,
208
+ ~entityName,
209
+ ~apiFieldName=Table.idFieldName,
210
+ ~operator="_eq",
211
+ ~value=id,
212
+ ~isArray=false,
213
+ ~params,
214
+ )
215
+ {
216
+ text: `SELECT * FROM ${quoted(
217
+ ~pgSchema,
218
+ ~tableName=table.tableName,
219
+ )} WHERE "${queryField.pgDbFieldName}" = ${placeholder} LIMIT 1;`,
220
+ params,
221
+ }
222
+ }
223
+
224
+ let decodeRows = (entityConfig: Internal.entityConfig, rows: array<unknown>): array<unknown> =>
225
+ rows->S.parseOrThrow(entityConfig.table->Table.pgRowsSchema)
226
+
227
+ type chainHeight = {
228
+ chainId: ChainId.t,
229
+ ecosystem: string,
230
+ startBlock: int,
231
+ endBlock: Null.t<int>,
232
+ sourceBlock: int,
233
+ bufferBlock: int,
234
+ progressBlock: int,
235
+ readyAt: Null.t<Date.t>,
236
+ isReady: bool,
237
+ }
238
+
239
+ // `envio_chains` is read by column rather than with `Table.pgRowsSchema`: the
240
+ // table's BIGINT columns come back from the driver as strings, which is why
241
+ // every other raw read of it casts on the way out. Only the watermark columns
242
+ // are selected, so none of those are in the result at all.
243
+ let chainColumns: array<InternalTable.Chains.field> = [
244
+ #id,
245
+ #ecosystem,
246
+ #start_block,
247
+ #end_block,
248
+ #source_block,
249
+ #buffer_block,
250
+ #progress_block,
251
+ #ready_at,
252
+ ]
253
+
254
+ type chainRow = {
255
+ id: unknown,
256
+ ecosystem: string,
257
+ startBlock: int,
258
+ endBlock: option<int>,
259
+ sourceBlock: int,
260
+ bufferBlock: int,
261
+ progressBlock: int,
262
+ readyAt: option<Date.t>,
263
+ }
264
+
265
+ let chainRowsSchema = S.array(
266
+ S.object((s): chainRow => {
267
+ id: s.field((#id: InternalTable.Chains.field :> string), S.unknown),
268
+ ecosystem: s.field((#ecosystem: InternalTable.Chains.field :> string), S.string),
269
+ startBlock: s.field((#start_block: InternalTable.Chains.field :> string), S.int),
270
+ endBlock: s.field((#end_block: InternalTable.Chains.field :> string), S.null(S.int)),
271
+ sourceBlock: s.field((#source_block: InternalTable.Chains.field :> string), S.int),
272
+ bufferBlock: s.field((#buffer_block: InternalTable.Chains.field :> string), S.int),
273
+ progressBlock: s.field((#progress_block: InternalTable.Chains.field :> string), S.int),
274
+ readyAt: s.field(
275
+ (#ready_at: InternalTable.Chains.field :> string),
276
+ S.null(Utils.Schema.dbDate),
277
+ ),
278
+ }),
279
+ )
280
+
281
+ let makeChainHeightsQuery = (pgSchema: string): query => {
282
+ text: `SELECT ${chainColumns
283
+ ->Array.map(column => `"${(column :> string)}"`)
284
+ ->Array.join(", ")} FROM ${quoted(
285
+ ~pgSchema,
286
+ ~tableName=InternalTable.Chains.table.tableName,
287
+ )};`,
288
+ params: [],
289
+ }
290
+
291
+ let decodeChainHeights = (rows: array<unknown>): dict<chainHeight> => {
292
+ let heights = Dict.make()
293
+ rows
294
+ ->S.parseOrThrow(chainRowsSchema)
295
+ ->Array.forEach(row => {
296
+ let chainId = row.id->ChainId.normalizeOrThrow
297
+ heights->Dict.set(
298
+ chainId->ChainId.toString,
299
+ {
300
+ chainId,
301
+ ecosystem: row.ecosystem,
302
+ startBlock: row.startBlock,
303
+ endBlock: row.endBlock->Null.fromOption,
304
+ sourceBlock: row.sourceBlock,
305
+ bufferBlock: row.bufferBlock,
306
+ progressBlock: row.progressBlock,
307
+ readyAt: row.readyAt->Null.fromOption,
308
+ isReady: row.readyAt->Option.isSome,
309
+ },
310
+ )
311
+ })
312
+ heights
313
+ }
@@ -0,0 +1,220 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as Table from "../db/Table.res.mjs";
4
+ import * as Utils from "../Utils.res.mjs";
5
+ import * as Config from "../Config.res.mjs";
6
+ import * as ChainId from "../ChainId.res.mjs";
7
+ import * as Stdlib_Null from "@rescript/runtime/lib/es6/Stdlib_Null.js";
8
+ import * as Stdlib_JsExn from "@rescript/runtime/lib/es6/Stdlib_JsExn.js";
9
+ import * as InternalTable from "../db/InternalTable.res.mjs";
10
+ import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
11
+ import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
12
+ import * as S$RescriptSchema from "rescript-schema/src/S.res.mjs";
13
+ import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
14
+
15
+ function quoted(pgSchema, tableName) {
16
+ return `"` + pgSchema + `"."` + tableName + `"`;
17
+ }
18
+
19
+ function getQueryFieldOrThrow(table, entityName, apiFieldName, where) {
20
+ let queryField = Table.queryFields(table)[apiFieldName];
21
+ if (queryField !== undefined) {
22
+ return queryField;
23
+ } else {
24
+ return Stdlib_JsError.throwWithMessage(`Invalid field "` + apiFieldName + `" in db.find("` + entityName + `", { ` + where + `: ... }). The entity has no such field.`);
25
+ }
26
+ }
27
+
28
+ function bindOrThrow(queryField, entityName, apiFieldName, operator, value, isArray, params) {
29
+ let json;
30
+ try {
31
+ json = S$RescriptSchema.reverseConvertToJsonOrThrow(value, isArray ? queryField.arrayFieldSchema : queryField.fieldSchema);
32
+ } catch (raw_exn) {
33
+ let exn = Primitive_exceptions.internalToException(raw_exn);
34
+ let reason = exn.RE_EXN_ID === "JsExn" ? Stdlib_Option.getOr(Stdlib_JsExn.message(exn._1), "it isn't the field's type") : "it isn't the field's type";
35
+ json = Stdlib_JsError.throwWithMessage(`Invalid value for db.find("` + entityName + `", { where: { ` + apiFieldName + `: { ` + operator + `: ... } } }): ` + reason);
36
+ }
37
+ params.push(json);
38
+ return `$` + params.length.toString();
39
+ }
40
+
41
+ function makeWhereCondition(table, entityName, where, params) {
42
+ let apiFieldNames = Object.keys(where);
43
+ if (apiFieldNames.length === 0) {
44
+ Stdlib_JsError.throwWithMessage(`Empty where passed to db.find("` + entityName + `"). Drop it, or filter like { fieldName: { _eq: value } }.`);
45
+ }
46
+ return apiFieldNames.flatMap(apiFieldName => {
47
+ let queryField = getQueryFieldOrThrow(table, entityName, apiFieldName, "where");
48
+ let operators = where[apiFieldName];
49
+ if (typeof operators !== "object" || Array.isArray(operators) || operators === null) {
50
+ Stdlib_JsError.throwWithMessage(`Invalid value in db.find("` + entityName + `", { where: { ` + apiFieldName + `: ... } }). Provide an operator like { _eq: value }.`);
51
+ }
52
+ let operatorKeys = Object.keys(operators);
53
+ if (operatorKeys.length === 0) {
54
+ Stdlib_JsError.throwWithMessage(`Empty operator in db.find("` + entityName + `", { where: { ` + apiFieldName + `: {} } }). Valid operators are _eq, _gt, _lt, _gte, _lte, _in.`);
55
+ }
56
+ return operatorKeys.map(operator => {
57
+ let value = operators[operator];
58
+ let bind = isArrayOpt => {
59
+ let isArray = isArrayOpt !== undefined ? isArrayOpt : false;
60
+ return bindOrThrow(queryField, entityName, apiFieldName, operator, value, isArray, params);
61
+ };
62
+ let column = `"` + queryField.pgDbFieldName + `"`;
63
+ switch (operator) {
64
+ case "_eq" :
65
+ return column + ` = ` + bind(undefined);
66
+ case "_gt" :
67
+ return column + ` > ` + bind(undefined);
68
+ case "_gte" :
69
+ return column + ` >= ` + bind(undefined);
70
+ case "_in" :
71
+ if (!Array.isArray(value)) {
72
+ Stdlib_JsError.throwWithMessage(`Invalid value in db.find("` + entityName + `", { where: { ` + apiFieldName + `: { _in: ... } } }). _in takes an array.`);
73
+ }
74
+ return column + ` = ANY(` + bind(true) + `)`;
75
+ case "_lt" :
76
+ return column + ` < ` + bind(undefined);
77
+ case "_lte" :
78
+ return column + ` <= ` + bind(undefined);
79
+ default:
80
+ return Stdlib_JsError.throwWithMessage(`Invalid operator "` + operator + `" in db.find("` + entityName + `", { where: { ` + apiFieldName + `: { ` + operator + `: ... } } }). Valid operators are _eq, _gt, _lt, _gte, _lte, _in.`);
81
+ }
82
+ });
83
+ }).join(" AND ");
84
+ }
85
+
86
+ function makeOrderByClause(table, entityName, orderBy) {
87
+ if (orderBy.length !== 0) {
88
+ return " ORDER BY " + orderBy.map(param => {
89
+ let field = param.field;
90
+ let queryField = getQueryFieldOrThrow(table, entityName, field, "orderBy");
91
+ let other = param.direction.toLowerCase();
92
+ let direction;
93
+ switch (other) {
94
+ case "asc" :
95
+ direction = "ASC";
96
+ break;
97
+ case "desc" :
98
+ direction = "DESC";
99
+ break;
100
+ default:
101
+ direction = Stdlib_JsError.throwWithMessage(`Invalid direction "` + other + `" in db.find("` + entityName + `", { orderBy: [{ field: "` + field + `" }] }). Use "asc" or "desc".`);
102
+ }
103
+ return `"` + queryField.pgDbFieldName + `" ` + direction;
104
+ }).join(", ");
105
+ } else {
106
+ return "";
107
+ }
108
+ }
109
+
110
+ function makeBoundCount(keyword, entityName, count, params) {
111
+ if (count < 0 || Math.trunc(count) !== count) {
112
+ Stdlib_JsError.throwWithMessage(`Invalid ` + keyword.toLowerCase() + ` ` + count.toString() + ` in db.find("` + entityName + `"). It must be a non-negative whole number.`);
113
+ }
114
+ params.push(count);
115
+ return ` ` + keyword + ` $` + params.length.toString();
116
+ }
117
+
118
+ function makeFindQuery(options) {
119
+ let entityConfig = options.entityConfig;
120
+ let table = entityConfig.table;
121
+ let entityName = entityConfig.name;
122
+ let params = [];
123
+ let where = options.where;
124
+ let whereClause = where !== undefined ? " WHERE " + makeWhereCondition(table, entityName, where, params) : "";
125
+ let orderBy = options.orderBy;
126
+ let orderByClause = orderBy !== undefined ? makeOrderByClause(table, entityName, orderBy) : "";
127
+ let count = options.limit;
128
+ let limitClause = count !== undefined ? makeBoundCount("LIMIT", entityName, count, params) : "";
129
+ let count$1 = options.offset;
130
+ let offsetClause = count$1 !== undefined ? makeBoundCount("OFFSET", entityName, count$1, params) : "";
131
+ return {
132
+ text: `SELECT * FROM ` + quoted(options.pgSchema, table.tableName) + whereClause + orderByClause + limitClause + offsetClause + `;`,
133
+ params: params
134
+ };
135
+ }
136
+
137
+ function makeGetQuery(entityConfig, pgSchema, id) {
138
+ let table = entityConfig.table;
139
+ let entityName = entityConfig.name;
140
+ let match = Table.getChainIdField(table);
141
+ if (match !== undefined) {
142
+ Stdlib_JsError.throwWithMessage(`db.get("` + entityName + `", id) is ambiguous: the entity is per-chain, so the same id can exist on every chain. Use db.find("` + entityName + `", { where: { id: { _eq: id }, ` + Config.chainIdFieldName + `: { _eq: chainId } } }).`);
143
+ }
144
+ let params = [];
145
+ let queryField = getQueryFieldOrThrow(table, entityName, Table.idFieldName, "where");
146
+ let placeholder = bindOrThrow(queryField, entityName, Table.idFieldName, "_eq", id, false, params);
147
+ return {
148
+ text: `SELECT * FROM ` + quoted(pgSchema, table.tableName) + ` WHERE "` + queryField.pgDbFieldName + `" = ` + placeholder + ` LIMIT 1;`,
149
+ params: params
150
+ };
151
+ }
152
+
153
+ function decodeRows(entityConfig, rows) {
154
+ return S$RescriptSchema.parseOrThrow(rows, Table.pgRowsSchema(entityConfig.table));
155
+ }
156
+
157
+ let chainColumns = [
158
+ "id",
159
+ "ecosystem",
160
+ "start_block",
161
+ "end_block",
162
+ "source_block",
163
+ "buffer_block",
164
+ "progress_block",
165
+ "ready_at"
166
+ ];
167
+
168
+ let chainRowsSchema = S$RescriptSchema.array(S$RescriptSchema.object(s => ({
169
+ id: s.f("id", S$RescriptSchema.unknown),
170
+ ecosystem: s.f("ecosystem", S$RescriptSchema.string),
171
+ startBlock: s.f("start_block", S$RescriptSchema.int),
172
+ endBlock: s.f("end_block", S$RescriptSchema.$$null(S$RescriptSchema.int)),
173
+ sourceBlock: s.f("source_block", S$RescriptSchema.int),
174
+ bufferBlock: s.f("buffer_block", S$RescriptSchema.int),
175
+ progressBlock: s.f("progress_block", S$RescriptSchema.int),
176
+ readyAt: s.f("ready_at", S$RescriptSchema.$$null(Utils.Schema.dbDate))
177
+ })));
178
+
179
+ function makeChainHeightsQuery(pgSchema) {
180
+ return {
181
+ text: `SELECT ` + chainColumns.map(column => `"` + column + `"`).join(", ") + ` FROM ` + quoted(pgSchema, InternalTable.Chains.table.tableName) + `;`,
182
+ params: []
183
+ };
184
+ }
185
+
186
+ function decodeChainHeights(rows) {
187
+ let heights = {};
188
+ S$RescriptSchema.parseOrThrow(rows, chainRowsSchema).forEach(row => {
189
+ let chainId = ChainId.normalizeOrThrow(row.id);
190
+ heights[ChainId.toString(chainId)] = {
191
+ chainId: chainId,
192
+ ecosystem: row.ecosystem,
193
+ startBlock: row.startBlock,
194
+ endBlock: Stdlib_Null.fromOption(row.endBlock),
195
+ sourceBlock: row.sourceBlock,
196
+ bufferBlock: row.bufferBlock,
197
+ progressBlock: row.progressBlock,
198
+ readyAt: Stdlib_Null.fromOption(row.readyAt),
199
+ isReady: Stdlib_Option.isSome(row.readyAt)
200
+ };
201
+ });
202
+ return heights;
203
+ }
204
+
205
+ export {
206
+ quoted,
207
+ getQueryFieldOrThrow,
208
+ bindOrThrow,
209
+ makeWhereCondition,
210
+ makeOrderByClause,
211
+ makeBoundCount,
212
+ makeFindQuery,
213
+ makeGetQuery,
214
+ decodeRows,
215
+ chainColumns,
216
+ chainRowsSchema,
217
+ makeChainHeightsQuery,
218
+ decodeChainHeights,
219
+ }
220
+ /* chainRowsSchema Not a pure module */
@@ -0,0 +1,108 @@
1
+ // Names envio-serve derives from schema.graphql, which a custom resolver must
2
+ // not squat.
3
+ //
4
+ // This mirrors the builder in envio-serve's `gql/schema_build.rs`: the two are
5
+ // one contract, and drift shows up as a schema serve refuses to start on.
6
+ // Serve does re-check at startup, deliberately -- but by then the user is
7
+ // reading a deployment log, and the build is where they can act. Hence both,
8
+ // as §6.1 asks.
9
+ //
10
+ // Not covered here: the `<scalar>_comparison_exp` types serve derives from
11
+ // column types. Mapping an entity field's type to serve's scalar name is not
12
+ // something this side can do without guessing, and serve's own check is the
13
+ // backstop for it.
14
+
15
+ const ROOT_AND_BUILTIN_TYPES = [
16
+ "query_root",
17
+ "subscription_root",
18
+ "order_by",
19
+ "cursor_ordering",
20
+ "Boolean",
21
+ "Float",
22
+ "ID",
23
+ "Int",
24
+ "String",
25
+ ];
26
+
27
+ const rootFieldsOf = (entity) => [
28
+ [entity, `entity '${entity}'`],
29
+ [`${entity}_by_pk`, `the by-primary-key field of entity '${entity}'`],
30
+ [`${entity}_aggregate`, `the aggregate field of entity '${entity}'`],
31
+ [`${entity}_stream`, `the subscription stream of entity '${entity}'`],
32
+ ];
33
+
34
+ const typesOf = (entity) =>
35
+ [
36
+ "",
37
+ "_aggregate",
38
+ "_aggregate_bool_exp",
39
+ "_aggregate_bool_exp_count",
40
+ "_aggregate_fields",
41
+ "_aggregate_order_by",
42
+ "_bool_exp",
43
+ "_order_by",
44
+ "_select_column",
45
+ "_stream_cursor_input",
46
+ "_stream_cursor_value_input",
47
+ ].map((suffix) => [
48
+ `${entity}${suffix}`,
49
+ suffix === ""
50
+ ? `the row type of entity '${entity}'`
51
+ : `a type generated for entity '${entity}'`,
52
+ ]);
53
+
54
+ /** Every generated name, mapped to how it would read in an error. */
55
+ export function reservedNames({ entities = [], enums = [] }) {
56
+ const rootFields = new Map();
57
+ const types = new Map();
58
+
59
+ for (const [name, describe] of ROOT_AND_BUILTIN_TYPES.map((name) => [
60
+ name,
61
+ "a built-in GraphQL type name",
62
+ ])) {
63
+ types.set(name, describe);
64
+ }
65
+ for (const name of enums) {
66
+ types.set(name, "an enum declared in schema.graphql");
67
+ }
68
+ for (const entity of entities) {
69
+ for (const [name, describe] of rootFieldsOf(entity)) {
70
+ rootFields.set(name, describe);
71
+ }
72
+ for (const [name, describe] of typesOf(entity)) {
73
+ types.set(name, describe);
74
+ }
75
+ }
76
+ return { rootFields, types };
77
+ }
78
+
79
+ /**
80
+ * Throws when a manifest declares a root field or type name envio-serve
81
+ * generates. Reports all of them at once: renaming one at a time through a
82
+ * build each is the slow way to find out there were three.
83
+ */
84
+ export function checkCollisions(manifest, { entities, enums }) {
85
+ const reserved = reservedNames({ entities, enums });
86
+ const found = [];
87
+
88
+ for (const resolver of manifest.resolvers) {
89
+ const describe = reserved.rootFields.get(resolver.name);
90
+ if (describe !== undefined) {
91
+ found.push(` - resolver '${resolver.name}' is ${describe}`);
92
+ }
93
+ }
94
+ for (const type of manifest.types) {
95
+ const describe = reserved.types.get(type.name);
96
+ if (describe !== undefined) {
97
+ found.push(` - type '${type.name}' is ${describe}`);
98
+ }
99
+ }
100
+
101
+ if (found.length > 0) {
102
+ throw new Error(
103
+ `Custom resolvers collide with names envio-serve generates from schema.graphql:\n${found.join(
104
+ "\n"
105
+ )}\nRename them: envio-serve owns those names and refuses to start on an ambiguous schema.`
106
+ );
107
+ }
108
+ }