@m1212e/rumble 0.22.1 → 0.23.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/out/index.cjs +504 -107
- package/out/index.cjs.map +1 -1
- package/out/index.d.cts +112 -15
- package/out/index.d.cts.map +1 -1
- package/out/index.d.mts +112 -15
- package/out/index.d.mts.map +1 -1
- package/out/index.mjs +507 -110
- package/out/index.mjs.map +1 -1
- package/package.json +1 -1
package/out/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { t as lazy } from "./lazy-C7IyM4M4.mjs";
|
|
2
2
|
import { t as generateFromSchema } from "./generate-0QFVc3Sg.mjs";
|
|
3
|
-
import { GraphQLError } from "graphql";
|
|
3
|
+
import { GraphQLError, GraphQLSchema } from "graphql";
|
|
4
4
|
import { EnvelopArmorPlugin } from "@escape.tech/graphql-armor";
|
|
5
5
|
import { useDisableIntrospection } from "@graphql-yoga/plugin-disable-introspection";
|
|
6
6
|
import { SpanStatusCode, trace } from "@opentelemetry/api";
|
|
@@ -10,14 +10,14 @@ import { createPubSub, createYoga } from "graphql-yoga";
|
|
|
10
10
|
import { One, count, getTableColumns, getTableName, is, isTable, relationsFilterToSQL, sql } from "drizzle-orm";
|
|
11
11
|
import { toCamelCase } from "drizzle-orm/casing";
|
|
12
12
|
import { PgColumn, PgEnumColumn, PgEnumObjectColumn, PgTable, isPgEnum } from "drizzle-orm/pg-core";
|
|
13
|
+
import { MySqlEnumColumn, MySqlEnumObjectColumn, MySqlTable } from "drizzle-orm/mysql-core";
|
|
13
14
|
import pluralize from "pluralize";
|
|
14
|
-
import { MySqlTable } from "drizzle-orm/mysql-core";
|
|
15
15
|
import { SQLiteTable } from "drizzle-orm/sqlite-core";
|
|
16
16
|
import SchemaBuilder, { BasePlugin } from "@pothos/core";
|
|
17
17
|
import DrizzlePlugin from "@pothos/plugin-drizzle";
|
|
18
18
|
import SmartSubscriptionsPlugin, { subscribeOptionsFromIterator } from "@pothos/plugin-smart-subscriptions";
|
|
19
|
-
import TracingPlugin, { isRootField } from "@pothos/plugin-tracing";
|
|
20
|
-
import { DateResolver, DateTimeISOResolver, JSONResolver } from "graphql-scalars";
|
|
19
|
+
import TracingPlugin, { isRootField, wrapResolver } from "@pothos/plugin-tracing";
|
|
20
|
+
import { BigIntResolver, ByteResolver, DateResolver, DateTimeISOResolver, JSONResolver } from "graphql-scalars";
|
|
21
21
|
//#region lib/types/rumbleError.ts
|
|
22
22
|
/**
|
|
23
23
|
* An error that gets raised by rumble whenever something does not go according to plan.
|
|
@@ -180,7 +180,7 @@ function mapNullFieldsToUndefined(obj) {
|
|
|
180
180
|
}
|
|
181
181
|
//#endregion
|
|
182
182
|
//#region package.json
|
|
183
|
-
var version = "0.
|
|
183
|
+
var version = "0.23.0";
|
|
184
184
|
//#endregion
|
|
185
185
|
//#region lib/helpers/mergeFilters.ts
|
|
186
186
|
function mergeFilters(filterA, filterB, mode = "AND") {
|
|
@@ -199,23 +199,47 @@ function mergeFilters(filterA, filterB, mode = "AND") {
|
|
|
199
199
|
}
|
|
200
200
|
//#endregion
|
|
201
201
|
//#region lib/helpers/sqlTypes/types.ts
|
|
202
|
+
/**
|
|
203
|
+
* Strips parameter suffixes like `(256)`, `(10,2)` and timezone modifiers
|
|
204
|
+
* (`with time zone`, `without time zone`) from raw getSQLType() output so
|
|
205
|
+
* every classifier works on the base type name only.
|
|
206
|
+
*/
|
|
207
|
+
function normalizeSQLType(raw) {
|
|
208
|
+
return raw.replace(/\s*with(?:out)?\s+time\s+zone/i, "").replace(/\s*\([^)]*\)/, "").trim().toLowerCase();
|
|
209
|
+
}
|
|
202
210
|
const intLikeSQLTypeStrings = [
|
|
203
211
|
"serial",
|
|
204
212
|
"int",
|
|
205
213
|
"integer",
|
|
206
214
|
"tinyint",
|
|
207
215
|
"smallint",
|
|
208
|
-
"mediumint"
|
|
216
|
+
"mediumint",
|
|
217
|
+
"int2",
|
|
218
|
+
"int4",
|
|
219
|
+
"smallserial"
|
|
209
220
|
];
|
|
210
221
|
function isIntLikeSQLTypeString(sqlType) {
|
|
211
222
|
return intLikeSQLTypeStrings.includes(sqlType);
|
|
212
223
|
}
|
|
224
|
+
const bigIntLikeSQLTypeStrings = [
|
|
225
|
+
"bigint",
|
|
226
|
+
"bigserial",
|
|
227
|
+
"int8",
|
|
228
|
+
"bigint unsigned"
|
|
229
|
+
];
|
|
230
|
+
function isBigIntLikeSQLTypeString(sqlType) {
|
|
231
|
+
return bigIntLikeSQLTypeStrings.includes(sqlType);
|
|
232
|
+
}
|
|
213
233
|
const floatLikeSQLTypeStrings = [
|
|
214
234
|
"real",
|
|
215
235
|
"decimal",
|
|
216
236
|
"double",
|
|
217
237
|
"float",
|
|
218
|
-
"numeric"
|
|
238
|
+
"numeric",
|
|
239
|
+
"double precision",
|
|
240
|
+
"float4",
|
|
241
|
+
"float8",
|
|
242
|
+
"money"
|
|
219
243
|
];
|
|
220
244
|
function isFloatLikeSQLTypeString(sqlType) {
|
|
221
245
|
return floatLikeSQLTypeStrings.includes(sqlType);
|
|
@@ -225,7 +249,20 @@ const stringLikeSQLTypeStrings = [
|
|
|
225
249
|
"text",
|
|
226
250
|
"varchar",
|
|
227
251
|
"char",
|
|
228
|
-
"
|
|
252
|
+
"character varying",
|
|
253
|
+
"character",
|
|
254
|
+
"inet",
|
|
255
|
+
"cidr",
|
|
256
|
+
"macaddr",
|
|
257
|
+
"macaddr8",
|
|
258
|
+
"interval",
|
|
259
|
+
"time",
|
|
260
|
+
"year",
|
|
261
|
+
"binary",
|
|
262
|
+
"varbinary",
|
|
263
|
+
"tinytext",
|
|
264
|
+
"mediumtext",
|
|
265
|
+
"longtext"
|
|
229
266
|
];
|
|
230
267
|
function isStringLikeSQLTypeString(sqlType) {
|
|
231
268
|
return stringLikeSQLTypeStrings.includes(sqlType);
|
|
@@ -234,7 +271,7 @@ const IDLikeSQLTypeStrings = ["uuid"];
|
|
|
234
271
|
function isIDLikeSQLTypeString(sqlType) {
|
|
235
272
|
return IDLikeSQLTypeStrings.includes(sqlType);
|
|
236
273
|
}
|
|
237
|
-
const booleanSQLTypeStrings = ["boolean"];
|
|
274
|
+
const booleanSQLTypeStrings = ["boolean", "bool"];
|
|
238
275
|
function isBooleanSQLTypeString(sqlType) {
|
|
239
276
|
return booleanSQLTypeStrings.includes(sqlType);
|
|
240
277
|
}
|
|
@@ -250,16 +287,23 @@ const jsonLikeSQLTypeStrings = ["json", "jsonb"];
|
|
|
250
287
|
function isJSONLikeSQLTypeString(sqlType) {
|
|
251
288
|
return jsonLikeSQLTypeStrings.includes(sqlType);
|
|
252
289
|
}
|
|
253
|
-
[
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
290
|
+
const jsonFallbackSQLTypeStrings = [
|
|
291
|
+
"point",
|
|
292
|
+
"line",
|
|
293
|
+
"lseg",
|
|
294
|
+
"box",
|
|
295
|
+
"path",
|
|
296
|
+
"polygon",
|
|
297
|
+
"circle",
|
|
298
|
+
"geometry"
|
|
262
299
|
];
|
|
300
|
+
function isJSONFallbackSQLTypeString(sqlType) {
|
|
301
|
+
return jsonFallbackSQLTypeStrings.includes(sqlType) || sqlType.startsWith("geometry");
|
|
302
|
+
}
|
|
303
|
+
const bytesSQLTypeStrings = ["bytea", "blob"];
|
|
304
|
+
function isBytesSQLTypeString(sqlType) {
|
|
305
|
+
return bytesSQLTypeStrings.includes(sqlType);
|
|
306
|
+
}
|
|
263
307
|
const UnknownTypeRumbleError = (sqlType, additionalInfo) => new RumbleError(`RumbleError: Unknown SQL type '${sqlType}'. Please open an issue (https://github.com/m1212e/rumble/issues) so it can be added. (${additionalInfo})`);
|
|
264
308
|
//#endregion
|
|
265
309
|
//#region lib/helpers/sqlTypes/distinctValuesFromSQLType.ts
|
|
@@ -374,16 +418,17 @@ function isDynamicQueryFilter(filter) {
|
|
|
374
418
|
function isStaticQueryFilter(filter) {
|
|
375
419
|
return typeof filter !== "function";
|
|
376
420
|
}
|
|
377
|
-
const
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
`);
|
|
421
|
+
const makeNothingRegisteredWarner = (logger) => debounce((model, action) => {
|
|
422
|
+
const msg = `No abilities registered for ${model}/${action} — blocking everything. Register the ability or ignore this warning if intentional.`;
|
|
423
|
+
if (logger) logger.warn({
|
|
424
|
+
"rumble.table": model,
|
|
425
|
+
"rumble.action": action
|
|
426
|
+
}, msg);
|
|
427
|
+
else console.warn(msg);
|
|
385
428
|
}, 1e3);
|
|
386
|
-
const createAbilityBuilder = ({ db, actions, defaultLimit, otel }) => {
|
|
429
|
+
const createAbilityBuilder = ({ db, actions, defaultLimit, otel, logger: loggerConfig }) => {
|
|
430
|
+
const log = loggerConfig?.enabled ? loggerConfig.logger : void 0;
|
|
431
|
+
const nothingRegisteredWarningLogger = makeNothingRegisteredWarner(log);
|
|
387
432
|
let hasBeenBuilt = false;
|
|
388
433
|
const createBuilderForTable = () => {
|
|
389
434
|
const queryFilters = /* @__PURE__ */ new Map();
|
|
@@ -673,14 +718,38 @@ by: (explicitFilter) => {
|
|
|
673
718
|
span?.setAttribute("abilities.status", "applied");
|
|
674
719
|
return transformToResponse(mergedFilters);
|
|
675
720
|
};
|
|
676
|
-
if (otel?.enabled && otel.tracer) return otel.tracer.startActiveSpan(`
|
|
721
|
+
if (otel?.enabled && otel.tracer) return otel.tracer.startActiveSpan(`rumble.abilities.prepare`, (span) => {
|
|
722
|
+
span.setAttribute("rumble.action", action);
|
|
723
|
+
span.setAttribute("rumble.table", tableName.toString());
|
|
677
724
|
try {
|
|
678
|
-
|
|
725
|
+
const attrs = {
|
|
726
|
+
"rumble.table": tableName.toString(),
|
|
727
|
+
"rumble.action": action
|
|
728
|
+
};
|
|
729
|
+
const result = assembleAbilities(new Proxy(span, { get(target, prop) {
|
|
730
|
+
if (prop === "setAttribute") return (k, v) => {
|
|
731
|
+
attrs[k] = v;
|
|
732
|
+
return target.setAttribute(k, v);
|
|
733
|
+
};
|
|
734
|
+
return target[prop];
|
|
735
|
+
} }));
|
|
736
|
+
log?.debug(attrs, "abilities prepared");
|
|
737
|
+
return result;
|
|
679
738
|
} finally {
|
|
680
739
|
span.end();
|
|
681
740
|
}
|
|
682
741
|
});
|
|
683
|
-
else
|
|
742
|
+
else {
|
|
743
|
+
const attrs = {
|
|
744
|
+
"rumble.table": tableName.toString(),
|
|
745
|
+
"rumble.action": action
|
|
746
|
+
};
|
|
747
|
+
const result = assembleAbilities(log ? { setAttribute: (k, v) => {
|
|
748
|
+
attrs[k] = v;
|
|
749
|
+
} } : void 0);
|
|
750
|
+
log?.debug(attrs, "abilities prepared");
|
|
751
|
+
return result;
|
|
752
|
+
}
|
|
684
753
|
} };
|
|
685
754
|
} };
|
|
686
755
|
};
|
|
@@ -709,29 +778,14 @@ const createOrderArgImplementer = ({ db, schemaBuilder }) => {
|
|
|
709
778
|
if (ret) return ret;
|
|
710
779
|
const implement = () => {
|
|
711
780
|
return schemaBuilder.inputType(inputTypeName, { fields: (t) => {
|
|
712
|
-
|
|
781
|
+
return Object.entries(tableSchema.columns).reduce((acc, [key]) => {
|
|
782
|
+
if (key.startsWith("__")) return acc;
|
|
713
783
|
acc[key] = t.field({
|
|
714
784
|
type: sortingParameterEnumRef(),
|
|
715
785
|
required: false
|
|
716
786
|
});
|
|
717
787
|
return acc;
|
|
718
788
|
}, {});
|
|
719
|
-
const relations = Object.entries(tableSchema.relations ?? {}).reduce((acc, [key, value]) => {
|
|
720
|
-
const targetTsName = value.targetTableName;
|
|
721
|
-
const referenceModel = orderArgImplementer({ dbName: tableHelper({
|
|
722
|
-
db,
|
|
723
|
-
table: targetTsName
|
|
724
|
-
}).dbName });
|
|
725
|
-
acc[key] = t.field({
|
|
726
|
-
type: referenceModel,
|
|
727
|
-
required: false
|
|
728
|
-
});
|
|
729
|
-
return acc;
|
|
730
|
-
}, {});
|
|
731
|
-
return {
|
|
732
|
-
...fields,
|
|
733
|
-
...relations
|
|
734
|
-
};
|
|
735
789
|
} });
|
|
736
790
|
};
|
|
737
791
|
ret = implement();
|
|
@@ -743,10 +797,10 @@ const createOrderArgImplementer = ({ db, schemaBuilder }) => {
|
|
|
743
797
|
//#endregion
|
|
744
798
|
//#region lib/enum.ts
|
|
745
799
|
/**
|
|
746
|
-
* Checks if a column is a
|
|
800
|
+
* Checks if a column is a database enum column (Postgres or MySQL).
|
|
747
801
|
*/
|
|
748
802
|
function isEnumSchema(schemaType) {
|
|
749
|
-
return is(schemaType, PgEnumColumn) || is(schemaType, PgEnumObjectColumn);
|
|
803
|
+
return is(schemaType, PgEnumColumn) || is(schemaType, PgEnumObjectColumn) || is(schemaType, MySqlEnumColumn) || is(schemaType, MySqlEnumObjectColumn);
|
|
750
804
|
}
|
|
751
805
|
const createEnumImplementer = ({ schema, schemaBuilder }) => {
|
|
752
806
|
const referenceStorage = /* @__PURE__ */ new Map();
|
|
@@ -778,7 +832,12 @@ const createEnumImplementer = ({ schema, schemaBuilder }) => {
|
|
|
778
832
|
if (enumObject) {
|
|
779
833
|
enumSchemaName = enumObject.enumName;
|
|
780
834
|
enumValues = enumObject.enumValues;
|
|
781
|
-
} else if (enumColumn) {
|
|
835
|
+
} else if (enumColumn) if (is(enumColumn, MySqlEnumColumn) || is(enumColumn, MySqlEnumObjectColumn)) {
|
|
836
|
+
enumValues = enumColumn.enumValues;
|
|
837
|
+
const colTable = enumColumn.table;
|
|
838
|
+
const tableName = colTable && isTable(colTable) ? getTableName(colTable) : void 0;
|
|
839
|
+
enumSchemaName = tableName ? `${tableName}_${enumColumn.name}` : enumColumn.name;
|
|
840
|
+
} else {
|
|
782
841
|
const pgEnum = enumColumn.enum;
|
|
783
842
|
if (!pgEnum) throw new RumbleError(`Could not find enum definition on column. Make sure the column is a pgEnum column.`);
|
|
784
843
|
enumSchemaName = pgEnum.enumName;
|
|
@@ -797,16 +856,21 @@ const createEnumImplementer = ({ schema, schemaBuilder }) => {
|
|
|
797
856
|
//#endregion
|
|
798
857
|
//#region lib/helpers/sqlTypes/mapSQLTypeToTSType.ts
|
|
799
858
|
function mapSQLTypeToGraphQLType({ sqlType, fieldName }) {
|
|
859
|
+
if (/^enum\(/.test(sqlType)) return "String";
|
|
860
|
+
const normalized = normalizeSQLType(sqlType);
|
|
800
861
|
let ret;
|
|
801
|
-
if (isIntLikeSQLTypeString(
|
|
802
|
-
if (
|
|
803
|
-
if (
|
|
862
|
+
if (isIntLikeSQLTypeString(normalized)) ret = "Int";
|
|
863
|
+
if (isBigIntLikeSQLTypeString(normalized)) ret = "BigInt";
|
|
864
|
+
if (isFloatLikeSQLTypeString(normalized)) ret = "Float";
|
|
865
|
+
if (isStringLikeSQLTypeString(normalized)) if (fieldName && (fieldName.toLowerCase().endsWith("_id") || fieldName.toLowerCase().endsWith("id"))) ret = "ID";
|
|
804
866
|
else ret = "String";
|
|
805
|
-
if (isIDLikeSQLTypeString(
|
|
806
|
-
if (isBooleanSQLTypeString(
|
|
807
|
-
if (isDateTimeLikeSQLTypeString(
|
|
808
|
-
if (isDateLikeSQLTypeString(
|
|
809
|
-
if (isJSONLikeSQLTypeString(
|
|
867
|
+
if (isIDLikeSQLTypeString(normalized)) ret = "ID";
|
|
868
|
+
if (isBooleanSQLTypeString(normalized)) ret = "Boolean";
|
|
869
|
+
if (isDateTimeLikeSQLTypeString(normalized)) ret = "DateTime";
|
|
870
|
+
if (isDateLikeSQLTypeString(normalized)) ret = "Date";
|
|
871
|
+
if (isJSONLikeSQLTypeString(normalized)) ret = "JSON";
|
|
872
|
+
if (isJSONFallbackSQLTypeString(normalized)) ret = "JSON";
|
|
873
|
+
if (isBytesSQLTypeString(normalized)) ret = "Bytes";
|
|
810
874
|
if (ret !== void 0) return ret;
|
|
811
875
|
throw UnknownTypeRumbleError(sqlType, "SQL to GQL");
|
|
812
876
|
}
|
|
@@ -816,6 +880,60 @@ const makeDefaultName = (dbName) => `${capitalize(toCamelCase(dbName.toString())
|
|
|
816
880
|
const createWhereArgImplementer = ({ db, schemaBuilder, enumImplementer }) => {
|
|
817
881
|
const referenceStorage = /* @__PURE__ */ new Map();
|
|
818
882
|
const enumArrayWhereInputStorage = /* @__PURE__ */ new Map();
|
|
883
|
+
const scalarArrayWhereInputStorage = /* @__PURE__ */ new Map();
|
|
884
|
+
const scalarArrayWhereInputName = (gqlType) => `${gqlType}ArrayWhereInputArgument`;
|
|
885
|
+
const getScalarArrayWhereInput = (gqlType) => {
|
|
886
|
+
const typeName = scalarArrayWhereInputName(gqlType);
|
|
887
|
+
const existing = scalarArrayWhereInputStorage.get(typeName);
|
|
888
|
+
if (existing) return existing;
|
|
889
|
+
const ret = schemaBuilder.inputRef(typeName);
|
|
890
|
+
scalarArrayWhereInputStorage.set(typeName, ret);
|
|
891
|
+
ret.implement({ fields: (t) => ({
|
|
892
|
+
eq: t.field({
|
|
893
|
+
type: [gqlType],
|
|
894
|
+
required: false
|
|
895
|
+
}),
|
|
896
|
+
ne: t.field({
|
|
897
|
+
type: [gqlType],
|
|
898
|
+
required: false
|
|
899
|
+
}),
|
|
900
|
+
in: t.field({
|
|
901
|
+
type: [[gqlType]],
|
|
902
|
+
required: false
|
|
903
|
+
}),
|
|
904
|
+
notIn: t.field({
|
|
905
|
+
type: [[gqlType]],
|
|
906
|
+
required: false
|
|
907
|
+
}),
|
|
908
|
+
isNull: t.boolean({ required: false }),
|
|
909
|
+
isNotNull: t.boolean({ required: false }),
|
|
910
|
+
arrayOverlaps: t.field({
|
|
911
|
+
type: [gqlType],
|
|
912
|
+
required: false
|
|
913
|
+
}),
|
|
914
|
+
arrayContained: t.field({
|
|
915
|
+
type: [gqlType],
|
|
916
|
+
required: false
|
|
917
|
+
}),
|
|
918
|
+
arrayContains: t.field({
|
|
919
|
+
type: [gqlType],
|
|
920
|
+
required: false
|
|
921
|
+
}),
|
|
922
|
+
AND: t.field({
|
|
923
|
+
type: [ret],
|
|
924
|
+
required: false
|
|
925
|
+
}),
|
|
926
|
+
OR: t.field({
|
|
927
|
+
type: [ret],
|
|
928
|
+
required: false
|
|
929
|
+
}),
|
|
930
|
+
NOT: t.field({
|
|
931
|
+
type: ret,
|
|
932
|
+
required: false
|
|
933
|
+
})
|
|
934
|
+
}) });
|
|
935
|
+
return ret;
|
|
936
|
+
};
|
|
819
937
|
const getEnumArrayWhereInput = (enumImpl) => {
|
|
820
938
|
const typeName = `${enumImpl.name}WhereInputArgument`;
|
|
821
939
|
const existing = enumArrayWhereInputStorage.get(typeName);
|
|
@@ -824,19 +942,19 @@ const createWhereArgImplementer = ({ db, schemaBuilder, enumImplementer }) => {
|
|
|
824
942
|
enumArrayWhereInputStorage.set(typeName, ret);
|
|
825
943
|
ret.implement({ fields: (t) => ({
|
|
826
944
|
eq: t.field({
|
|
827
|
-
type: enumImpl,
|
|
945
|
+
type: [enumImpl],
|
|
828
946
|
required: false
|
|
829
947
|
}),
|
|
830
948
|
ne: t.field({
|
|
831
|
-
type: enumImpl,
|
|
949
|
+
type: [enumImpl],
|
|
832
950
|
required: false
|
|
833
951
|
}),
|
|
834
952
|
in: t.field({
|
|
835
|
-
type: [enumImpl],
|
|
953
|
+
type: [[enumImpl]],
|
|
836
954
|
required: false
|
|
837
955
|
}),
|
|
838
956
|
notIn: t.field({
|
|
839
|
-
type: [enumImpl],
|
|
957
|
+
type: [[enumImpl]],
|
|
840
958
|
required: false
|
|
841
959
|
}),
|
|
842
960
|
isNull: t.boolean({ required: false }),
|
|
@@ -879,31 +997,73 @@ const createWhereArgImplementer = ({ db, schemaBuilder, enumImplementer }) => {
|
|
|
879
997
|
const ret = schemaBuilder.inputRef(inputTypeName);
|
|
880
998
|
referenceStorage.set(inputTypeName, ret);
|
|
881
999
|
ret.implement({ fields: (t) => {
|
|
882
|
-
const mapSQLTypeStringToInputPothosType = (sqlType, fieldName) => {
|
|
1000
|
+
const mapSQLTypeStringToInputPothosType = (sqlType, fieldName, isArray) => {
|
|
883
1001
|
const gqlType = mapSQLTypeToGraphQLType({
|
|
884
1002
|
sqlType,
|
|
885
1003
|
fieldName
|
|
886
1004
|
});
|
|
1005
|
+
if (isArray) return t.field({
|
|
1006
|
+
type: getScalarArrayWhereInput(gqlType),
|
|
1007
|
+
required: false
|
|
1008
|
+
});
|
|
887
1009
|
switch (gqlType) {
|
|
888
|
-
case "Int": return t.field({
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
case "
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
1010
|
+
case "Int": return t.field({
|
|
1011
|
+
type: "IntWhereInputArgument",
|
|
1012
|
+
required: false
|
|
1013
|
+
});
|
|
1014
|
+
case "BigInt": return t.field({
|
|
1015
|
+
type: "BigIntWhereInputArgument",
|
|
1016
|
+
required: false
|
|
1017
|
+
});
|
|
1018
|
+
case "String": return t.field({
|
|
1019
|
+
type: "StringWhereInputArgument",
|
|
1020
|
+
required: false
|
|
1021
|
+
});
|
|
1022
|
+
case "Boolean": return t.field({
|
|
1023
|
+
type: "BooleanWhereInputArgument",
|
|
1024
|
+
required: false
|
|
1025
|
+
});
|
|
1026
|
+
case "Date": return t.field({
|
|
1027
|
+
type: "DateWhereInputArgument",
|
|
1028
|
+
required: false
|
|
1029
|
+
});
|
|
1030
|
+
case "DateTime": return t.field({
|
|
1031
|
+
type: "DateTimeWhereInputArgument",
|
|
1032
|
+
required: false
|
|
1033
|
+
});
|
|
1034
|
+
case "Float": return t.field({
|
|
1035
|
+
type: "FloatWhereInputArgument",
|
|
1036
|
+
required: false
|
|
1037
|
+
});
|
|
1038
|
+
case "ID": return t.field({
|
|
1039
|
+
type: "IDWhereInputArgument",
|
|
1040
|
+
required: false
|
|
1041
|
+
});
|
|
1042
|
+
case "JSON": return t.field({
|
|
1043
|
+
type: "JSONWhereInputArgument",
|
|
1044
|
+
required: false
|
|
1045
|
+
});
|
|
1046
|
+
case "Bytes": return null;
|
|
896
1047
|
default: throw new RumbleError(`Unsupported argument type ${gqlType} for column ${sqlType}`);
|
|
897
1048
|
}
|
|
898
1049
|
};
|
|
899
1050
|
const fields = Object.entries(tableSchema.columns).reduce((acc, [key, value]) => {
|
|
1051
|
+
if (key.startsWith("__")) return acc;
|
|
1052
|
+
let isArray = false;
|
|
1053
|
+
if (is(value, PgColumn)) {
|
|
1054
|
+
const dims = value.dimensions;
|
|
1055
|
+
if (dims && dims > 0) isArray = true;
|
|
1056
|
+
}
|
|
900
1057
|
if (isEnumSchema(value)) {
|
|
901
1058
|
const enumImpl = enumImplementer({ enumColumn: value });
|
|
902
1059
|
acc[key] = t.field({
|
|
903
|
-
type:
|
|
1060
|
+
type: isArray ? getEnumArrayWhereInput(enumImpl) : enumImpl,
|
|
904
1061
|
required: false
|
|
905
1062
|
});
|
|
906
|
-
} else
|
|
1063
|
+
} else {
|
|
1064
|
+
const field = mapSQLTypeStringToInputPothosType(value.getSQLType(), key, isArray);
|
|
1065
|
+
if (field !== null) acc[key] = field;
|
|
1066
|
+
}
|
|
907
1067
|
return acc;
|
|
908
1068
|
}, {});
|
|
909
1069
|
const relations = Object.entries(tableSchema.relations ?? {}).reduce((acc, [key, value]) => {
|
|
@@ -1117,6 +1277,11 @@ function buildPothosResponseTypeFromGraphQLType({ builder, sqlType, fieldName, n
|
|
|
1117
1277
|
});
|
|
1118
1278
|
switch (gqlType) {
|
|
1119
1279
|
case "Int": return isArray ? builder.exposeIntList(fieldName, { nullable }) : builder.exposeInt(fieldName, { nullable });
|
|
1280
|
+
case "BigInt": return builder.field({
|
|
1281
|
+
type: isArray ? ["BigInt"] : "BigInt",
|
|
1282
|
+
resolve: (element) => element[fieldName],
|
|
1283
|
+
nullable
|
|
1284
|
+
});
|
|
1120
1285
|
case "String": return isArray ? builder.exposeStringList(fieldName, { nullable }) : builder.exposeString(fieldName, { nullable });
|
|
1121
1286
|
case "Boolean": return isArray ? builder.exposeBooleanList(fieldName, { nullable }) : builder.exposeBoolean(fieldName, { nullable });
|
|
1122
1287
|
case "Date": return builder.field({
|
|
@@ -1126,7 +1291,11 @@ function buildPothosResponseTypeFromGraphQLType({ builder, sqlType, fieldName, n
|
|
|
1126
1291
|
});
|
|
1127
1292
|
case "DateTime": return builder.field({
|
|
1128
1293
|
type: isArray ? ["DateTime"] : "DateTime",
|
|
1129
|
-
resolve: (element) =>
|
|
1294
|
+
resolve: (element) => {
|
|
1295
|
+
const v = element[fieldName];
|
|
1296
|
+
if (v == null) return v;
|
|
1297
|
+
return v instanceof Date ? v : new Date(v);
|
|
1298
|
+
},
|
|
1130
1299
|
nullable
|
|
1131
1300
|
});
|
|
1132
1301
|
case "Float": return isArray ? builder.exposeFloatList(fieldName, { nullable }) : builder.exposeFloat(fieldName, { nullable });
|
|
@@ -1136,6 +1305,15 @@ function buildPothosResponseTypeFromGraphQLType({ builder, sqlType, fieldName, n
|
|
|
1136
1305
|
resolve: (element) => element[fieldName],
|
|
1137
1306
|
nullable
|
|
1138
1307
|
});
|
|
1308
|
+
case "Bytes": return builder.field({
|
|
1309
|
+
type: "Bytes",
|
|
1310
|
+
resolve: (element) => {
|
|
1311
|
+
const v = element[fieldName];
|
|
1312
|
+
if (v == null) return v;
|
|
1313
|
+
return Buffer.isBuffer(v) ? v.toString("base64") : String(v);
|
|
1314
|
+
},
|
|
1315
|
+
nullable
|
|
1316
|
+
});
|
|
1139
1317
|
default: throw new RumbleError(`Unsupported object type ${gqlType} for column ${fieldName}`);
|
|
1140
1318
|
}
|
|
1141
1319
|
}
|
|
@@ -1167,14 +1345,17 @@ function isPostgresDB(db) {
|
|
|
1167
1345
|
//#endregion
|
|
1168
1346
|
//#region lib/search.ts
|
|
1169
1347
|
async function initSearchIfApplicable(input) {
|
|
1348
|
+
const log = input.logger?.enabled ? input.logger.logger : void 0;
|
|
1170
1349
|
if (!isPostgresDB(input.db)) {
|
|
1171
|
-
|
|
1350
|
+
const msg = "Database dialect is not compatible with search, skipping search initialization. Only PostgreSQL is supported.";
|
|
1351
|
+
log ? log.info({}, msg) : console.info(msg);
|
|
1172
1352
|
return;
|
|
1173
1353
|
}
|
|
1174
1354
|
try {
|
|
1175
1355
|
await input.db.execute(sql`CREATE EXTENSION IF NOT EXISTS pg_trgm;`);
|
|
1176
1356
|
} catch (error) {
|
|
1177
|
-
|
|
1357
|
+
const msg = "Failed to create pg_trgm extension. Search functionality may not work. Ensure the database user has CREATE privilege or the extension is pre-installed.";
|
|
1358
|
+
log ? log.error({ err: error }, msg) : console.error(msg, error);
|
|
1178
1359
|
return;
|
|
1179
1360
|
}
|
|
1180
1361
|
if (input.search?.threshold) {
|
|
@@ -1233,13 +1414,17 @@ const isProbablyAConfigObject = (t) => {
|
|
|
1233
1414
|
].find((e) => e === k))) return true;
|
|
1234
1415
|
return false;
|
|
1235
1416
|
};
|
|
1236
|
-
const createObjectImplementer = ({ db, search, schemaBuilder, makePubSubInstance, whereArgImplementer, orderArgImplementer, enumImplementer, abilityBuilder }) => {
|
|
1417
|
+
const createObjectImplementer = ({ db, search, logger: loggerConfig, schemaBuilder, makePubSubInstance, whereArgImplementer, orderArgImplementer, enumImplementer, abilityBuilder }) => {
|
|
1237
1418
|
return ({ table, refName, readAction = "read", adjust }) => {
|
|
1238
1419
|
const tableSchema = tableHelper({
|
|
1239
1420
|
db,
|
|
1240
1421
|
table
|
|
1241
1422
|
});
|
|
1242
|
-
if (Object.keys(tableSchema.primaryKey).length === 0)
|
|
1423
|
+
if (Object.keys(tableSchema.primaryKey).length === 0) {
|
|
1424
|
+
const log = loggerConfig?.enabled ? loggerConfig.logger : void 0;
|
|
1425
|
+
const msg = `Could not find primary key for ${table.toString()}. Cannot register subscriptions!`;
|
|
1426
|
+
log ? log.warn({ "rumble.table": table.toString() }, msg) : console.warn(msg);
|
|
1427
|
+
}
|
|
1243
1428
|
const primaryKey = Object.values(tableSchema.primaryKey)[0];
|
|
1244
1429
|
const { registerOnInstance } = makePubSubInstance({ table });
|
|
1245
1430
|
return schemaBuilder.drizzleObject(table, {
|
|
@@ -1248,7 +1433,9 @@ const createObjectImplementer = ({ db, search, schemaBuilder, makePubSubInstance
|
|
|
1248
1433
|
if (!primaryKey) return;
|
|
1249
1434
|
const primaryKeyValue = element[primaryKey.name];
|
|
1250
1435
|
if (!primaryKeyValue) {
|
|
1251
|
-
|
|
1436
|
+
const log = loggerConfig?.enabled ? loggerConfig.logger : void 0;
|
|
1437
|
+
const msg = `Could not find primary key value for element on ${table.toString()}. Cannot register subscription!`;
|
|
1438
|
+
log ? log.warn({ "rumble.table": table.toString() }, msg) : console.warn(msg);
|
|
1252
1439
|
return;
|
|
1253
1440
|
}
|
|
1254
1441
|
registerOnInstance({
|
|
@@ -1279,6 +1466,10 @@ const createObjectImplementer = ({ db, search, schemaBuilder, makePubSubInstance
|
|
|
1279
1466
|
};
|
|
1280
1467
|
} })) ?? {};
|
|
1281
1468
|
const fields = Object.entries(columns).reduce((acc, [key, column]) => {
|
|
1469
|
+
if (key.startsWith("__")) {
|
|
1470
|
+
console.warn(`Skipping column "${key}" on table "${tableSchema.tsName}": names starting with "__" are reserved by GraphQL introspection. Use a custom adjust() call if you really need to expose this column.`);
|
|
1471
|
+
return acc;
|
|
1472
|
+
}
|
|
1282
1473
|
if (userAdjustments[key]) {
|
|
1283
1474
|
const { params, creatorFunction, configObject } = configMap.get(userAdjustments[key]);
|
|
1284
1475
|
if (typeof configObject.nullable !== "boolean") configObject.nullable = !column.notNull;
|
|
@@ -1479,7 +1670,8 @@ const createPubSubInstance = ({ subscriptions }) => {
|
|
|
1479
1670
|
};
|
|
1480
1671
|
//#endregion
|
|
1481
1672
|
//#region lib/query.ts
|
|
1482
|
-
const createQueryImplementer = ({ db, schemaBuilder, search, whereArgImplementer, orderArgImplementer, makePubSubInstance }) => {
|
|
1673
|
+
const createQueryImplementer = ({ db, schemaBuilder, search, logger: loggerConfig, whereArgImplementer, orderArgImplementer, makePubSubInstance }) => {
|
|
1674
|
+
const registeredQueryFieldNames = /* @__PURE__ */ new Set();
|
|
1483
1675
|
return ({ table, readAction = "read", listAction = "read" }) => {
|
|
1484
1676
|
const WhereArg = whereArgImplementer({ table });
|
|
1485
1677
|
const OrderArg = orderArgImplementer({ table });
|
|
@@ -1489,6 +1681,12 @@ const createQueryImplementer = ({ db, schemaBuilder, search, whereArgImplementer
|
|
|
1489
1681
|
});
|
|
1490
1682
|
const primaryKeyField = Object.values(tableSchema.primaryKey)[0];
|
|
1491
1683
|
const { registerOnInstance } = makePubSubInstance({ table });
|
|
1684
|
+
const pluralName = pluralize.plural(table.toString());
|
|
1685
|
+
const singularName = pluralize.singular(table.toString());
|
|
1686
|
+
for (const name of [pluralName, singularName]) {
|
|
1687
|
+
if (registeredQueryFieldNames.has(name)) throw new RumbleError(`Duplicate query field "${name}": the table "${table.toString()}" generates a query field that collides with one already registered. Use unique table names (or refNames) to avoid plural/singular collisions (e.g. "post" and "posts").`);
|
|
1688
|
+
registeredQueryFieldNames.add(name);
|
|
1689
|
+
}
|
|
1492
1690
|
return schemaBuilder.queryFields((t) => {
|
|
1493
1691
|
const manyArgs = {
|
|
1494
1692
|
where: t.arg({
|
|
@@ -1505,11 +1703,11 @@ const createQueryImplementer = ({ db, schemaBuilder, search, whereArgImplementer
|
|
|
1505
1703
|
};
|
|
1506
1704
|
if (!search?.enabled) delete manyArgs.search;
|
|
1507
1705
|
return {
|
|
1508
|
-
[
|
|
1706
|
+
[pluralName]: t.drizzleField({
|
|
1509
1707
|
type: [table],
|
|
1510
1708
|
nullable: false,
|
|
1511
1709
|
smartSubscription: true,
|
|
1512
|
-
description: `List all ${
|
|
1710
|
+
description: `List all ${pluralName}`,
|
|
1513
1711
|
subscribe: (subscriptions, _root, _args, _ctx, _info) => {
|
|
1514
1712
|
registerOnInstance({
|
|
1515
1713
|
instance: subscriptions,
|
|
@@ -1537,17 +1735,21 @@ const createQueryImplementer = ({ db, schemaBuilder, search, whereArgImplementer
|
|
|
1537
1735
|
if (filter.columns) queryInstance.columns = filter.columns;
|
|
1538
1736
|
if (search?.enabled && args.search && args.search.length > 0 && search?.cpu_operator_cost) return db.transaction(async (tx) => {
|
|
1539
1737
|
if (isPostgresDB(tx)) await tx.execute(sql.raw(`SET cpu_operator_cost = ${search.cpu_operator_cost};`));
|
|
1540
|
-
else
|
|
1738
|
+
else {
|
|
1739
|
+
const msg = "Database dialect is not postgresql, cannot set cpu_operator_cost.";
|
|
1740
|
+
const log = loggerConfig?.enabled ? loggerConfig.logger : void 0;
|
|
1741
|
+
log ? log.info({}, msg) : console.info(msg);
|
|
1742
|
+
}
|
|
1541
1743
|
return tx.query[table].findMany(queryInstance);
|
|
1542
1744
|
});
|
|
1543
1745
|
return db.query[table].findMany(queryInstance);
|
|
1544
1746
|
}
|
|
1545
1747
|
}),
|
|
1546
|
-
[
|
|
1748
|
+
[singularName]: t.drizzleField({
|
|
1547
1749
|
type: table,
|
|
1548
1750
|
nullable: false,
|
|
1549
1751
|
smartSubscription: true,
|
|
1550
|
-
description: `Get a single ${
|
|
1752
|
+
description: `Get a single ${singularName} by ID`,
|
|
1551
1753
|
args: { id: t.arg.id({ required: true }) },
|
|
1552
1754
|
resolve: (query, _root, args, ctx, _info) => {
|
|
1553
1755
|
deepSetProto(args);
|
|
@@ -1723,6 +1925,96 @@ function implementDefaultWhereInputArgs(schemaBuilder) {
|
|
|
1723
1925
|
OR: t.field({ type: [JSONWhereInputArgument] }),
|
|
1724
1926
|
NOT: t.field({ type: JSONWhereInputArgument })
|
|
1725
1927
|
}) });
|
|
1928
|
+
const BigIntWhereInputArgument = schemaBuilder.inputRef("BigIntWhereInputArgument").implement({ fields: (t) => ({
|
|
1929
|
+
eq: t.field({
|
|
1930
|
+
type: "BigInt",
|
|
1931
|
+
required: false
|
|
1932
|
+
}),
|
|
1933
|
+
ne: t.field({
|
|
1934
|
+
type: "BigInt",
|
|
1935
|
+
required: false
|
|
1936
|
+
}),
|
|
1937
|
+
gt: t.field({
|
|
1938
|
+
type: "BigInt",
|
|
1939
|
+
required: false
|
|
1940
|
+
}),
|
|
1941
|
+
gte: t.field({
|
|
1942
|
+
type: "BigInt",
|
|
1943
|
+
required: false
|
|
1944
|
+
}),
|
|
1945
|
+
lt: t.field({
|
|
1946
|
+
type: "BigInt",
|
|
1947
|
+
required: false
|
|
1948
|
+
}),
|
|
1949
|
+
lte: t.field({
|
|
1950
|
+
type: "BigInt",
|
|
1951
|
+
required: false
|
|
1952
|
+
}),
|
|
1953
|
+
in: t.field({
|
|
1954
|
+
type: ["BigInt"],
|
|
1955
|
+
required: false
|
|
1956
|
+
}),
|
|
1957
|
+
notIn: t.field({
|
|
1958
|
+
type: ["BigInt"],
|
|
1959
|
+
required: false
|
|
1960
|
+
}),
|
|
1961
|
+
isNull: t.boolean(),
|
|
1962
|
+
isNotNull: t.boolean(),
|
|
1963
|
+
AND: t.field({ type: [BigIntWhereInputArgument] }),
|
|
1964
|
+
OR: t.field({ type: [BigIntWhereInputArgument] }),
|
|
1965
|
+
NOT: t.field({ type: BigIntWhereInputArgument })
|
|
1966
|
+
}) });
|
|
1967
|
+
const DateTimeWhereInputArgument = schemaBuilder.inputRef("DateTimeWhereInputArgument").implement({ fields: (t) => ({
|
|
1968
|
+
eq: t.field({
|
|
1969
|
+
type: "DateTime",
|
|
1970
|
+
required: false
|
|
1971
|
+
}),
|
|
1972
|
+
ne: t.field({
|
|
1973
|
+
type: "DateTime",
|
|
1974
|
+
required: false
|
|
1975
|
+
}),
|
|
1976
|
+
gt: t.field({
|
|
1977
|
+
type: "DateTime",
|
|
1978
|
+
required: false
|
|
1979
|
+
}),
|
|
1980
|
+
gte: t.field({
|
|
1981
|
+
type: "DateTime",
|
|
1982
|
+
required: false
|
|
1983
|
+
}),
|
|
1984
|
+
lt: t.field({
|
|
1985
|
+
type: "DateTime",
|
|
1986
|
+
required: false
|
|
1987
|
+
}),
|
|
1988
|
+
lte: t.field({
|
|
1989
|
+
type: "DateTime",
|
|
1990
|
+
required: false
|
|
1991
|
+
}),
|
|
1992
|
+
in: t.field({
|
|
1993
|
+
type: ["DateTime"],
|
|
1994
|
+
required: false
|
|
1995
|
+
}),
|
|
1996
|
+
notIn: t.field({
|
|
1997
|
+
type: ["DateTime"],
|
|
1998
|
+
required: false
|
|
1999
|
+
}),
|
|
2000
|
+
isNull: t.boolean(),
|
|
2001
|
+
isNotNull: t.boolean(),
|
|
2002
|
+
arrayOverlaps: t.field({
|
|
2003
|
+
type: ["DateTime"],
|
|
2004
|
+
required: false
|
|
2005
|
+
}),
|
|
2006
|
+
arrayContained: t.field({
|
|
2007
|
+
type: ["DateTime"],
|
|
2008
|
+
required: false
|
|
2009
|
+
}),
|
|
2010
|
+
arrayContains: t.field({
|
|
2011
|
+
type: ["DateTime"],
|
|
2012
|
+
required: false
|
|
2013
|
+
}),
|
|
2014
|
+
AND: t.field({ type: [DateTimeWhereInputArgument] }),
|
|
2015
|
+
OR: t.field({ type: [DateTimeWhereInputArgument] }),
|
|
2016
|
+
NOT: t.field({ type: DateTimeWhereInputArgument })
|
|
2017
|
+
}) });
|
|
1726
2018
|
}
|
|
1727
2019
|
//#endregion
|
|
1728
2020
|
//#region lib/runtimeFiltersPlugin/filterTypes.ts
|
|
@@ -1733,9 +2025,11 @@ const applyFiltersKey = "applyFilters";
|
|
|
1733
2025
|
var RuntimeFiltersPlugin = class extends BasePlugin {
|
|
1734
2026
|
tracer;
|
|
1735
2027
|
tracerEnabled;
|
|
2028
|
+
logger;
|
|
1736
2029
|
onTypeConfig(typeConfig) {
|
|
1737
2030
|
this.tracer = this.builder.options.otel?.tracer;
|
|
1738
2031
|
this.tracerEnabled = this.builder.options.otel?.enabled;
|
|
2032
|
+
this.logger = this.builder.options.logger?.enabled ? this.builder.options.logger.logger : void 0;
|
|
1739
2033
|
return typeConfig;
|
|
1740
2034
|
}
|
|
1741
2035
|
wrapResolve(resolver, fieldConfig) {
|
|
@@ -1764,7 +2058,8 @@ var RuntimeFiltersPlugin = class extends BasePlugin {
|
|
|
1764
2058
|
}));
|
|
1765
2059
|
let resolved, prefetchedFilters;
|
|
1766
2060
|
if (this.tracer && this.tracerEnabled) {
|
|
1767
|
-
const o = await this.tracer.startActiveSpan(`
|
|
2061
|
+
const o = await this.tracer.startActiveSpan(`rumble.filter.resolve`, async (span) => {
|
|
2062
|
+
span.setAttribute("graphql.field.name", fieldConfig.name);
|
|
1768
2063
|
try {
|
|
1769
2064
|
return await Promise.all([resolver(parent, args, context, info), prefetchedFiltersPromises]);
|
|
1770
2065
|
} finally {
|
|
@@ -1786,17 +2081,37 @@ var RuntimeFiltersPlugin = class extends BasePlugin {
|
|
|
1786
2081
|
return acc;
|
|
1787
2082
|
}, /* @__PURE__ */ new Set()));
|
|
1788
2083
|
span?.setAttribute("filters.allowed", allowed.length);
|
|
2084
|
+
this.logger?.debug({
|
|
2085
|
+
"graphql.field.name": fieldConfig.name,
|
|
2086
|
+
"filters.total": allFilters.length,
|
|
2087
|
+
"filters.allowed": allowed.length
|
|
2088
|
+
}, "runtime filters applied");
|
|
1789
2089
|
if (Array.isArray(resolved)) return allowed;
|
|
1790
2090
|
return allowed[0] ?? null;
|
|
1791
2091
|
};
|
|
1792
|
-
if (this.tracer && this.tracerEnabled) return this.tracer.startActiveSpan(`
|
|
2092
|
+
if (this.tracer && this.tracerEnabled) return this.tracer.startActiveSpan(`rumble.filter.apply`, async (span) => {
|
|
2093
|
+
span.setAttribute("graphql.field.name", fieldConfig.name);
|
|
1793
2094
|
try {
|
|
1794
2095
|
return await runFilters(span);
|
|
2096
|
+
} catch (error) {
|
|
2097
|
+
this.logger?.error({
|
|
2098
|
+
"graphql.field.name": fieldConfig.name,
|
|
2099
|
+
err: error
|
|
2100
|
+
}, "runtime filter threw");
|
|
2101
|
+
throw error;
|
|
1795
2102
|
} finally {
|
|
1796
2103
|
span.end();
|
|
1797
2104
|
}
|
|
1798
2105
|
});
|
|
1799
|
-
else
|
|
2106
|
+
else try {
|
|
2107
|
+
return await runFilters();
|
|
2108
|
+
} catch (error) {
|
|
2109
|
+
this.logger?.error({
|
|
2110
|
+
"graphql.field.name": fieldConfig.name,
|
|
2111
|
+
err: error
|
|
2112
|
+
}, "runtime filter threw");
|
|
2113
|
+
throw error;
|
|
2114
|
+
}
|
|
1800
2115
|
};
|
|
1801
2116
|
}
|
|
1802
2117
|
};
|
|
@@ -1809,7 +2124,7 @@ function registerRuntimeFiltersPlugin() {
|
|
|
1809
2124
|
}
|
|
1810
2125
|
//#endregion
|
|
1811
2126
|
//#region lib/schemaBuilder.ts
|
|
1812
|
-
const createSchemaBuilder = ({ db,
|
|
2127
|
+
const createSchemaBuilder = ({ db, pubsub, pothosConfig, otel, logger }) => {
|
|
1813
2128
|
const createSpan = otel?.enabled && otel.tracer ? createOpenTelemetryWrapper(otel.tracer, otel.options) : void 0;
|
|
1814
2129
|
registerRuntimeFiltersPlugin();
|
|
1815
2130
|
const schemaBuilder = new SchemaBuilder({
|
|
@@ -1837,18 +2152,40 @@ const createSchemaBuilder = ({ db, disableDefaultObjects, pubsub, pothosConfig,
|
|
|
1837
2152
|
}) },
|
|
1838
2153
|
defaultFieldNullability: false,
|
|
1839
2154
|
tracing: {
|
|
1840
|
-
default: otel?.enabled ? (config) => isRootField(config) : () => false,
|
|
1841
|
-
wrap:
|
|
2155
|
+
default: otel?.enabled || logger?.enabled ? (config) => isRootField(config) : () => false,
|
|
2156
|
+
wrap: (resolver, options, config) => {
|
|
2157
|
+
let r = createSpan ? createSpan(resolver, options) : resolver;
|
|
2158
|
+
if (logger?.enabled) {
|
|
2159
|
+
const log = logger.logger;
|
|
2160
|
+
r = wrapResolver(r, (error, duration) => {
|
|
2161
|
+
if (error) log.error({
|
|
2162
|
+
"graphql.field.name": config.name,
|
|
2163
|
+
"graphql.parent.type": config.parentType,
|
|
2164
|
+
durationMs: duration,
|
|
2165
|
+
err: error
|
|
2166
|
+
}, "resolver failed");
|
|
2167
|
+
else log.debug({
|
|
2168
|
+
"graphql.field.name": config.name,
|
|
2169
|
+
"graphql.parent.type": config.parentType,
|
|
2170
|
+
durationMs: duration
|
|
2171
|
+
}, "resolver completed");
|
|
2172
|
+
});
|
|
2173
|
+
}
|
|
2174
|
+
return r;
|
|
2175
|
+
}
|
|
1842
2176
|
},
|
|
1843
|
-
otel
|
|
2177
|
+
otel,
|
|
2178
|
+
logger
|
|
1844
2179
|
});
|
|
1845
2180
|
schemaBuilder.addScalarType("JSON", JSONResolver);
|
|
1846
2181
|
schemaBuilder.addScalarType("Date", DateResolver);
|
|
1847
2182
|
schemaBuilder.addScalarType("DateTime", DateTimeISOResolver);
|
|
2183
|
+
schemaBuilder.addScalarType("BigInt", BigIntResolver);
|
|
2184
|
+
schemaBuilder.addScalarType("Bytes", ByteResolver);
|
|
1848
2185
|
implementDefaultWhereInputArgs(schemaBuilder);
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
2186
|
+
schemaBuilder.queryType({});
|
|
2187
|
+
schemaBuilder.subscriptionType({});
|
|
2188
|
+
schemaBuilder.mutationType({});
|
|
1852
2189
|
return { schemaBuilder };
|
|
1853
2190
|
};
|
|
1854
2191
|
//#endregion
|
|
@@ -1931,7 +2268,33 @@ export const r = rumble({
|
|
|
1931
2268
|
whereArgImplementer: whereArg,
|
|
1932
2269
|
makePubSubInstance
|
|
1933
2270
|
});
|
|
1934
|
-
const builtSchema = lazy(() =>
|
|
2271
|
+
const builtSchema = lazy(() => {
|
|
2272
|
+
let raw;
|
|
2273
|
+
try {
|
|
2274
|
+
raw = schemaBuilder.toSchema();
|
|
2275
|
+
} catch (e) {
|
|
2276
|
+
const unimplMatch = (e?.message ?? String(e)).match(/ObjectRef<(.+?)> has not been implemented/);
|
|
2277
|
+
if (unimplMatch) throw new RumbleError(`Could not build GraphQL schema: the table "${unimplMatch[1]}" is referenced in a relation but has not been registered. Call rumble.object({ table: "${unimplMatch[1]}" }) before building the schema.`);
|
|
2278
|
+
throw e;
|
|
2279
|
+
}
|
|
2280
|
+
const mutation = raw.getMutationType();
|
|
2281
|
+
const subscription = raw.getSubscriptionType();
|
|
2282
|
+
const emptyMutation = mutation && Object.keys(mutation.getFields()).length === 0;
|
|
2283
|
+
const emptySubscription = subscription && Object.keys(subscription.getFields()).length === 0;
|
|
2284
|
+
if (!emptyMutation && !emptySubscription) return raw;
|
|
2285
|
+
const config = raw.toConfig();
|
|
2286
|
+
const filteredTypes = config.types.filter((t) => {
|
|
2287
|
+
if (emptyMutation && t === mutation) return false;
|
|
2288
|
+
if (emptySubscription && t === subscription) return false;
|
|
2289
|
+
return true;
|
|
2290
|
+
});
|
|
2291
|
+
return new GraphQLSchema({
|
|
2292
|
+
...config,
|
|
2293
|
+
mutation: emptyMutation ? void 0 : config.mutation,
|
|
2294
|
+
subscription: emptySubscription ? void 0 : config.subscription,
|
|
2295
|
+
types: filteredTypes
|
|
2296
|
+
});
|
|
2297
|
+
});
|
|
1935
2298
|
const createYoga$1 = (args) => {
|
|
1936
2299
|
const enableApiDocs = args?.enableApiDocs ?? process?.env?.NODE_ENV === "development";
|
|
1937
2300
|
return createYoga({
|
|
@@ -1942,26 +2305,60 @@ export const r = rumble({
|
|
|
1942
2305
|
plugins: [
|
|
1943
2306
|
...args?.plugins ?? [],
|
|
1944
2307
|
...enableApiDocs ? [] : [useDisableIntrospection(), EnvelopArmorPlugin(args?.armorConfig)],
|
|
1945
|
-
rumbleInput.otel?.enabled ? { onExecute: ({ setExecuteFn, executeFn }) => {
|
|
1946
|
-
setExecuteFn((options) =>
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
2308
|
+
rumbleInput.otel?.enabled || rumbleInput.logger?.enabled ? { onExecute: ({ setExecuteFn, executeFn }) => {
|
|
2309
|
+
setExecuteFn(async (options) => {
|
|
2310
|
+
const log = rumbleInput.logger?.enabled ? rumbleInput.logger.logger : void 0;
|
|
2311
|
+
const operationName = options.operationName ?? "anonymous";
|
|
2312
|
+
const start = Date.now();
|
|
2313
|
+
log?.info({ "graphql.operation.name": operationName }, "graphql execute start");
|
|
2314
|
+
const run = async () => {
|
|
1951
2315
|
const result = await executeFn(options);
|
|
1952
|
-
if (result && "errors" in result && result.errors?.length) {
|
|
1953
|
-
|
|
2316
|
+
if (result && "errors" in result && result.errors?.length) log?.error({
|
|
2317
|
+
"graphql.operation.name": operationName,
|
|
2318
|
+
durationMs: Date.now() - start,
|
|
2319
|
+
errors: result.errors.map((e) => e.message)
|
|
2320
|
+
}, "graphql execute completed with errors");
|
|
2321
|
+
else log?.info({
|
|
2322
|
+
"graphql.operation.name": operationName,
|
|
2323
|
+
durationMs: Date.now() - start
|
|
2324
|
+
}, "graphql execute completed");
|
|
2325
|
+
return result;
|
|
2326
|
+
};
|
|
2327
|
+
if (rumbleInput.otel?.enabled) return rumbleInput.otel.tracer.startActiveSpan(SpanNames.EXECUTE, { attributes: {
|
|
2328
|
+
[AttributeNames.OPERATION_NAME]: operationName,
|
|
2329
|
+
[AttributeNames.SOURCE]: options.document
|
|
2330
|
+
} }, async (span) => {
|
|
2331
|
+
try {
|
|
2332
|
+
const result = await run();
|
|
2333
|
+
if (result && "errors" in result && result.errors?.length) {
|
|
2334
|
+
for (const error of result.errors) span.recordException(error);
|
|
2335
|
+
span.setStatus({ code: SpanStatusCode.ERROR });
|
|
2336
|
+
}
|
|
2337
|
+
return result;
|
|
2338
|
+
} catch (error) {
|
|
2339
|
+
if (error instanceof Error) span.recordException(error);
|
|
2340
|
+
log?.error({
|
|
2341
|
+
"graphql.operation.name": operationName,
|
|
2342
|
+
durationMs: Date.now() - start,
|
|
2343
|
+
err: error
|
|
2344
|
+
}, "graphql execute threw");
|
|
1954
2345
|
span.setStatus({ code: SpanStatusCode.ERROR });
|
|
2346
|
+
throw error;
|
|
2347
|
+
} finally {
|
|
2348
|
+
span.end();
|
|
1955
2349
|
}
|
|
1956
|
-
|
|
2350
|
+
});
|
|
2351
|
+
try {
|
|
2352
|
+
return await run();
|
|
1957
2353
|
} catch (error) {
|
|
1958
|
-
|
|
1959
|
-
|
|
2354
|
+
log?.error({
|
|
2355
|
+
"graphql.operation.name": operationName,
|
|
2356
|
+
durationMs: Date.now() - start,
|
|
2357
|
+
err: error
|
|
2358
|
+
}, "graphql execute threw");
|
|
1960
2359
|
throw error;
|
|
1961
|
-
} finally {
|
|
1962
|
-
span.end();
|
|
1963
2360
|
}
|
|
1964
|
-
})
|
|
2361
|
+
});
|
|
1965
2362
|
} } : false
|
|
1966
2363
|
].filter(Boolean)
|
|
1967
2364
|
});
|