@m1212e/rumble 0.22.2 → 0.23.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/out/index.cjs +506 -106
- package/out/index.cjs.map +1 -1
- package/out/index.d.cts +115 -18
- package/out/index.d.cts.map +1 -1
- package/out/index.d.mts +115 -18
- package/out/index.d.mts.map +1 -1
- package/out/index.mjs +509 -109
- package/out/index.mjs.map +1 -1
- package/package.json +19 -19
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.2";
|
|
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();
|
|
@@ -631,7 +676,8 @@ by: (explicitFilter) => {
|
|
|
631
676
|
* Merges the current query filters with the provided filters for this call only
|
|
632
677
|
*/
|
|
633
678
|
function merge(p) {
|
|
634
|
-
|
|
679
|
+
const merged = mergeFilters(ret.query.many, p);
|
|
680
|
+
return internalTransformer(merged, p.limit);
|
|
635
681
|
}
|
|
636
682
|
ret.merge = merge;
|
|
637
683
|
return ret;
|
|
@@ -673,14 +719,39 @@ by: (explicitFilter) => {
|
|
|
673
719
|
span?.setAttribute("abilities.status", "applied");
|
|
674
720
|
return transformToResponse(mergedFilters);
|
|
675
721
|
};
|
|
676
|
-
if (otel?.enabled && otel.tracer) return otel.tracer.startActiveSpan(`
|
|
722
|
+
if (otel?.enabled && otel.tracer) return otel.tracer.startActiveSpan(`rumble.abilities.prepare`, (span) => {
|
|
723
|
+
span.setAttribute("rumble.action", action);
|
|
724
|
+
span.setAttribute("rumble.table", tableName.toString());
|
|
677
725
|
try {
|
|
678
|
-
|
|
726
|
+
const attrs = {
|
|
727
|
+
"rumble.table": tableName.toString(),
|
|
728
|
+
"rumble.action": action
|
|
729
|
+
};
|
|
730
|
+
const proxy = new Proxy(span, { get(target, prop) {
|
|
731
|
+
if (prop === "setAttribute") return (k, v) => {
|
|
732
|
+
attrs[k] = v;
|
|
733
|
+
return target.setAttribute(k, v);
|
|
734
|
+
};
|
|
735
|
+
return target[prop];
|
|
736
|
+
} });
|
|
737
|
+
const result = assembleAbilities(proxy);
|
|
738
|
+
log?.debug(attrs, "abilities prepared");
|
|
739
|
+
return result;
|
|
679
740
|
} finally {
|
|
680
741
|
span.end();
|
|
681
742
|
}
|
|
682
743
|
});
|
|
683
|
-
else
|
|
744
|
+
else {
|
|
745
|
+
const attrs = {
|
|
746
|
+
"rumble.table": tableName.toString(),
|
|
747
|
+
"rumble.action": action
|
|
748
|
+
};
|
|
749
|
+
const result = assembleAbilities(log ? { setAttribute: (k, v) => {
|
|
750
|
+
attrs[k] = v;
|
|
751
|
+
} } : void 0);
|
|
752
|
+
log?.debug(attrs, "abilities prepared");
|
|
753
|
+
return result;
|
|
754
|
+
}
|
|
684
755
|
} };
|
|
685
756
|
} };
|
|
686
757
|
};
|
|
@@ -709,29 +780,14 @@ const createOrderArgImplementer = ({ db, schemaBuilder }) => {
|
|
|
709
780
|
if (ret) return ret;
|
|
710
781
|
const implement = () => {
|
|
711
782
|
return schemaBuilder.inputType(inputTypeName, { fields: (t) => {
|
|
712
|
-
|
|
783
|
+
return Object.entries(tableSchema.columns).reduce((acc, [key]) => {
|
|
784
|
+
if (key.startsWith("__")) return acc;
|
|
713
785
|
acc[key] = t.field({
|
|
714
786
|
type: sortingParameterEnumRef(),
|
|
715
787
|
required: false
|
|
716
788
|
});
|
|
717
789
|
return acc;
|
|
718
790
|
}, {});
|
|
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
791
|
} });
|
|
736
792
|
};
|
|
737
793
|
ret = implement();
|
|
@@ -743,10 +799,10 @@ const createOrderArgImplementer = ({ db, schemaBuilder }) => {
|
|
|
743
799
|
//#endregion
|
|
744
800
|
//#region lib/enum.ts
|
|
745
801
|
/**
|
|
746
|
-
* Checks if a column is a
|
|
802
|
+
* Checks if a column is a database enum column (Postgres or MySQL).
|
|
747
803
|
*/
|
|
748
804
|
function isEnumSchema(schemaType) {
|
|
749
|
-
return is(schemaType, PgEnumColumn) || is(schemaType, PgEnumObjectColumn);
|
|
805
|
+
return is(schemaType, PgEnumColumn) || is(schemaType, PgEnumObjectColumn) || is(schemaType, MySqlEnumColumn) || is(schemaType, MySqlEnumObjectColumn);
|
|
750
806
|
}
|
|
751
807
|
const createEnumImplementer = ({ schema, schemaBuilder }) => {
|
|
752
808
|
const referenceStorage = /* @__PURE__ */ new Map();
|
|
@@ -778,7 +834,12 @@ const createEnumImplementer = ({ schema, schemaBuilder }) => {
|
|
|
778
834
|
if (enumObject) {
|
|
779
835
|
enumSchemaName = enumObject.enumName;
|
|
780
836
|
enumValues = enumObject.enumValues;
|
|
781
|
-
} else if (enumColumn) {
|
|
837
|
+
} else if (enumColumn) if (is(enumColumn, MySqlEnumColumn) || is(enumColumn, MySqlEnumObjectColumn)) {
|
|
838
|
+
enumValues = enumColumn.enumValues;
|
|
839
|
+
const colTable = enumColumn.table;
|
|
840
|
+
const tableName = colTable && isTable(colTable) ? getTableName(colTable) : void 0;
|
|
841
|
+
enumSchemaName = tableName ? `${tableName}_${enumColumn.name}` : enumColumn.name;
|
|
842
|
+
} else {
|
|
782
843
|
const pgEnum = enumColumn.enum;
|
|
783
844
|
if (!pgEnum) throw new RumbleError(`Could not find enum definition on column. Make sure the column is a pgEnum column.`);
|
|
784
845
|
enumSchemaName = pgEnum.enumName;
|
|
@@ -797,16 +858,21 @@ const createEnumImplementer = ({ schema, schemaBuilder }) => {
|
|
|
797
858
|
//#endregion
|
|
798
859
|
//#region lib/helpers/sqlTypes/mapSQLTypeToTSType.ts
|
|
799
860
|
function mapSQLTypeToGraphQLType({ sqlType, fieldName }) {
|
|
861
|
+
if (/^enum\(/.test(sqlType)) return "String";
|
|
862
|
+
const normalized = normalizeSQLType(sqlType);
|
|
800
863
|
let ret;
|
|
801
|
-
if (isIntLikeSQLTypeString(
|
|
802
|
-
if (
|
|
803
|
-
if (
|
|
864
|
+
if (isIntLikeSQLTypeString(normalized)) ret = "Int";
|
|
865
|
+
if (isBigIntLikeSQLTypeString(normalized)) ret = "BigInt";
|
|
866
|
+
if (isFloatLikeSQLTypeString(normalized)) ret = "Float";
|
|
867
|
+
if (isStringLikeSQLTypeString(normalized)) if (fieldName && (fieldName.toLowerCase().endsWith("_id") || fieldName.toLowerCase().endsWith("id"))) ret = "ID";
|
|
804
868
|
else ret = "String";
|
|
805
|
-
if (isIDLikeSQLTypeString(
|
|
806
|
-
if (isBooleanSQLTypeString(
|
|
807
|
-
if (isDateTimeLikeSQLTypeString(
|
|
808
|
-
if (isDateLikeSQLTypeString(
|
|
809
|
-
if (isJSONLikeSQLTypeString(
|
|
869
|
+
if (isIDLikeSQLTypeString(normalized)) ret = "ID";
|
|
870
|
+
if (isBooleanSQLTypeString(normalized)) ret = "Boolean";
|
|
871
|
+
if (isDateTimeLikeSQLTypeString(normalized)) ret = "DateTime";
|
|
872
|
+
if (isDateLikeSQLTypeString(normalized)) ret = "Date";
|
|
873
|
+
if (isJSONLikeSQLTypeString(normalized)) ret = "JSON";
|
|
874
|
+
if (isJSONFallbackSQLTypeString(normalized)) ret = "JSON";
|
|
875
|
+
if (isBytesSQLTypeString(normalized)) ret = "Bytes";
|
|
810
876
|
if (ret !== void 0) return ret;
|
|
811
877
|
throw UnknownTypeRumbleError(sqlType, "SQL to GQL");
|
|
812
878
|
}
|
|
@@ -816,6 +882,60 @@ const makeDefaultName = (dbName) => `${capitalize(toCamelCase(dbName.toString())
|
|
|
816
882
|
const createWhereArgImplementer = ({ db, schemaBuilder, enumImplementer }) => {
|
|
817
883
|
const referenceStorage = /* @__PURE__ */ new Map();
|
|
818
884
|
const enumArrayWhereInputStorage = /* @__PURE__ */ new Map();
|
|
885
|
+
const scalarArrayWhereInputStorage = /* @__PURE__ */ new Map();
|
|
886
|
+
const scalarArrayWhereInputName = (gqlType) => `${gqlType}ArrayWhereInputArgument`;
|
|
887
|
+
const getScalarArrayWhereInput = (gqlType) => {
|
|
888
|
+
const typeName = scalarArrayWhereInputName(gqlType);
|
|
889
|
+
const existing = scalarArrayWhereInputStorage.get(typeName);
|
|
890
|
+
if (existing) return existing;
|
|
891
|
+
const ret = schemaBuilder.inputRef(typeName);
|
|
892
|
+
scalarArrayWhereInputStorage.set(typeName, ret);
|
|
893
|
+
ret.implement({ fields: (t) => ({
|
|
894
|
+
eq: t.field({
|
|
895
|
+
type: [gqlType],
|
|
896
|
+
required: false
|
|
897
|
+
}),
|
|
898
|
+
ne: t.field({
|
|
899
|
+
type: [gqlType],
|
|
900
|
+
required: false
|
|
901
|
+
}),
|
|
902
|
+
in: t.field({
|
|
903
|
+
type: [[gqlType]],
|
|
904
|
+
required: false
|
|
905
|
+
}),
|
|
906
|
+
notIn: t.field({
|
|
907
|
+
type: [[gqlType]],
|
|
908
|
+
required: false
|
|
909
|
+
}),
|
|
910
|
+
isNull: t.boolean({ required: false }),
|
|
911
|
+
isNotNull: t.boolean({ required: false }),
|
|
912
|
+
arrayOverlaps: t.field({
|
|
913
|
+
type: [gqlType],
|
|
914
|
+
required: false
|
|
915
|
+
}),
|
|
916
|
+
arrayContained: t.field({
|
|
917
|
+
type: [gqlType],
|
|
918
|
+
required: false
|
|
919
|
+
}),
|
|
920
|
+
arrayContains: t.field({
|
|
921
|
+
type: [gqlType],
|
|
922
|
+
required: false
|
|
923
|
+
}),
|
|
924
|
+
AND: t.field({
|
|
925
|
+
type: [ret],
|
|
926
|
+
required: false
|
|
927
|
+
}),
|
|
928
|
+
OR: t.field({
|
|
929
|
+
type: [ret],
|
|
930
|
+
required: false
|
|
931
|
+
}),
|
|
932
|
+
NOT: t.field({
|
|
933
|
+
type: ret,
|
|
934
|
+
required: false
|
|
935
|
+
})
|
|
936
|
+
}) });
|
|
937
|
+
return ret;
|
|
938
|
+
};
|
|
819
939
|
const getEnumArrayWhereInput = (enumImpl) => {
|
|
820
940
|
const typeName = `${enumImpl.name}WhereInputArgument`;
|
|
821
941
|
const existing = enumArrayWhereInputStorage.get(typeName);
|
|
@@ -879,39 +999,82 @@ const createWhereArgImplementer = ({ db, schemaBuilder, enumImplementer }) => {
|
|
|
879
999
|
const ret = schemaBuilder.inputRef(inputTypeName);
|
|
880
1000
|
referenceStorage.set(inputTypeName, ret);
|
|
881
1001
|
ret.implement({ fields: (t) => {
|
|
882
|
-
const mapSQLTypeStringToInputPothosType = (sqlType, fieldName) => {
|
|
1002
|
+
const mapSQLTypeStringToInputPothosType = (sqlType, fieldName, isArray) => {
|
|
883
1003
|
const gqlType = mapSQLTypeToGraphQLType({
|
|
884
1004
|
sqlType,
|
|
885
1005
|
fieldName
|
|
886
1006
|
});
|
|
1007
|
+
if (isArray) return t.field({
|
|
1008
|
+
type: getScalarArrayWhereInput(gqlType),
|
|
1009
|
+
required: false
|
|
1010
|
+
});
|
|
887
1011
|
switch (gqlType) {
|
|
888
|
-
case "Int": return t.field({
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
case "
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
1012
|
+
case "Int": return t.field({
|
|
1013
|
+
type: "IntWhereInputArgument",
|
|
1014
|
+
required: false
|
|
1015
|
+
});
|
|
1016
|
+
case "BigInt": return t.field({
|
|
1017
|
+
type: "BigIntWhereInputArgument",
|
|
1018
|
+
required: false
|
|
1019
|
+
});
|
|
1020
|
+
case "String": return t.field({
|
|
1021
|
+
type: "StringWhereInputArgument",
|
|
1022
|
+
required: false
|
|
1023
|
+
});
|
|
1024
|
+
case "Boolean": return t.field({
|
|
1025
|
+
type: "BooleanWhereInputArgument",
|
|
1026
|
+
required: false
|
|
1027
|
+
});
|
|
1028
|
+
case "Date": return t.field({
|
|
1029
|
+
type: "DateWhereInputArgument",
|
|
1030
|
+
required: false
|
|
1031
|
+
});
|
|
1032
|
+
case "DateTime": return t.field({
|
|
1033
|
+
type: "DateTimeWhereInputArgument",
|
|
1034
|
+
required: false
|
|
1035
|
+
});
|
|
1036
|
+
case "Float": return t.field({
|
|
1037
|
+
type: "FloatWhereInputArgument",
|
|
1038
|
+
required: false
|
|
1039
|
+
});
|
|
1040
|
+
case "ID": return t.field({
|
|
1041
|
+
type: "IDWhereInputArgument",
|
|
1042
|
+
required: false
|
|
1043
|
+
});
|
|
1044
|
+
case "JSON": return t.field({
|
|
1045
|
+
type: "JSONWhereInputArgument",
|
|
1046
|
+
required: false
|
|
1047
|
+
});
|
|
1048
|
+
case "Bytes": return null;
|
|
896
1049
|
default: throw new RumbleError(`Unsupported argument type ${gqlType} for column ${sqlType}`);
|
|
897
1050
|
}
|
|
898
1051
|
};
|
|
899
1052
|
const fields = Object.entries(tableSchema.columns).reduce((acc, [key, value]) => {
|
|
1053
|
+
if (key.startsWith("__")) return acc;
|
|
1054
|
+
let isArray = false;
|
|
1055
|
+
if (is(value, PgColumn)) {
|
|
1056
|
+
const dims = value.dimensions;
|
|
1057
|
+
if (dims && dims > 0) isArray = true;
|
|
1058
|
+
}
|
|
900
1059
|
if (isEnumSchema(value)) {
|
|
901
1060
|
const enumImpl = enumImplementer({ enumColumn: value });
|
|
902
1061
|
acc[key] = t.field({
|
|
903
|
-
type:
|
|
1062
|
+
type: isArray ? getEnumArrayWhereInput(enumImpl) : enumImpl,
|
|
904
1063
|
required: false
|
|
905
1064
|
});
|
|
906
|
-
} else
|
|
1065
|
+
} else {
|
|
1066
|
+
const field = mapSQLTypeStringToInputPothosType(value.getSQLType(), key, isArray);
|
|
1067
|
+
if (field !== null) acc[key] = field;
|
|
1068
|
+
}
|
|
907
1069
|
return acc;
|
|
908
1070
|
}, {});
|
|
909
1071
|
const relations = Object.entries(tableSchema.relations ?? {}).reduce((acc, [key, value]) => {
|
|
910
1072
|
const targetTsName = value.targetTableName;
|
|
911
|
-
const
|
|
1073
|
+
const relationSchema = tableHelper({
|
|
912
1074
|
db,
|
|
913
1075
|
table: targetTsName
|
|
914
|
-
})
|
|
1076
|
+
});
|
|
1077
|
+
const referenceModel = whereArgImplementer({ dbName: relationSchema.dbName });
|
|
915
1078
|
acc[key] = t.field({
|
|
916
1079
|
type: referenceModel,
|
|
917
1080
|
required: false
|
|
@@ -1117,6 +1280,11 @@ function buildPothosResponseTypeFromGraphQLType({ builder, sqlType, fieldName, n
|
|
|
1117
1280
|
});
|
|
1118
1281
|
switch (gqlType) {
|
|
1119
1282
|
case "Int": return isArray ? builder.exposeIntList(fieldName, { nullable }) : builder.exposeInt(fieldName, { nullable });
|
|
1283
|
+
case "BigInt": return builder.field({
|
|
1284
|
+
type: isArray ? ["BigInt"] : "BigInt",
|
|
1285
|
+
resolve: (element) => element[fieldName],
|
|
1286
|
+
nullable
|
|
1287
|
+
});
|
|
1120
1288
|
case "String": return isArray ? builder.exposeStringList(fieldName, { nullable }) : builder.exposeString(fieldName, { nullable });
|
|
1121
1289
|
case "Boolean": return isArray ? builder.exposeBooleanList(fieldName, { nullable }) : builder.exposeBoolean(fieldName, { nullable });
|
|
1122
1290
|
case "Date": return builder.field({
|
|
@@ -1126,7 +1294,11 @@ function buildPothosResponseTypeFromGraphQLType({ builder, sqlType, fieldName, n
|
|
|
1126
1294
|
});
|
|
1127
1295
|
case "DateTime": return builder.field({
|
|
1128
1296
|
type: isArray ? ["DateTime"] : "DateTime",
|
|
1129
|
-
resolve: (element) =>
|
|
1297
|
+
resolve: (element) => {
|
|
1298
|
+
const v = element[fieldName];
|
|
1299
|
+
if (v == null) return v;
|
|
1300
|
+
return v instanceof Date ? v : new Date(v);
|
|
1301
|
+
},
|
|
1130
1302
|
nullable
|
|
1131
1303
|
});
|
|
1132
1304
|
case "Float": return isArray ? builder.exposeFloatList(fieldName, { nullable }) : builder.exposeFloat(fieldName, { nullable });
|
|
@@ -1136,6 +1308,15 @@ function buildPothosResponseTypeFromGraphQLType({ builder, sqlType, fieldName, n
|
|
|
1136
1308
|
resolve: (element) => element[fieldName],
|
|
1137
1309
|
nullable
|
|
1138
1310
|
});
|
|
1311
|
+
case "Bytes": return builder.field({
|
|
1312
|
+
type: "Bytes",
|
|
1313
|
+
resolve: (element) => {
|
|
1314
|
+
const v = element[fieldName];
|
|
1315
|
+
if (v == null) return v;
|
|
1316
|
+
return Buffer.isBuffer(v) ? v.toString("base64") : String(v);
|
|
1317
|
+
},
|
|
1318
|
+
nullable
|
|
1319
|
+
});
|
|
1139
1320
|
default: throw new RumbleError(`Unsupported object type ${gqlType} for column ${fieldName}`);
|
|
1140
1321
|
}
|
|
1141
1322
|
}
|
|
@@ -1167,14 +1348,17 @@ function isPostgresDB(db) {
|
|
|
1167
1348
|
//#endregion
|
|
1168
1349
|
//#region lib/search.ts
|
|
1169
1350
|
async function initSearchIfApplicable(input) {
|
|
1351
|
+
const log = input.logger?.enabled ? input.logger.logger : void 0;
|
|
1170
1352
|
if (!isPostgresDB(input.db)) {
|
|
1171
|
-
|
|
1353
|
+
const msg = "Database dialect is not compatible with search, skipping search initialization. Only PostgreSQL is supported.";
|
|
1354
|
+
log ? log.info({}, msg) : console.info(msg);
|
|
1172
1355
|
return;
|
|
1173
1356
|
}
|
|
1174
1357
|
try {
|
|
1175
1358
|
await input.db.execute(sql`CREATE EXTENSION IF NOT EXISTS pg_trgm;`);
|
|
1176
1359
|
} catch (error) {
|
|
1177
|
-
|
|
1360
|
+
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.";
|
|
1361
|
+
log ? log.error({ err: error }, msg) : console.error(msg, error);
|
|
1178
1362
|
return;
|
|
1179
1363
|
}
|
|
1180
1364
|
if (input.search?.threshold) {
|
|
@@ -1233,13 +1417,17 @@ const isProbablyAConfigObject = (t) => {
|
|
|
1233
1417
|
].find((e) => e === k))) return true;
|
|
1234
1418
|
return false;
|
|
1235
1419
|
};
|
|
1236
|
-
const createObjectImplementer = ({ db, search, schemaBuilder, makePubSubInstance, whereArgImplementer, orderArgImplementer, enumImplementer, abilityBuilder }) => {
|
|
1420
|
+
const createObjectImplementer = ({ db, search, logger: loggerConfig, schemaBuilder, makePubSubInstance, whereArgImplementer, orderArgImplementer, enumImplementer, abilityBuilder }) => {
|
|
1237
1421
|
return ({ table, refName, readAction = "read", adjust }) => {
|
|
1238
1422
|
const tableSchema = tableHelper({
|
|
1239
1423
|
db,
|
|
1240
1424
|
table
|
|
1241
1425
|
});
|
|
1242
|
-
if (Object.keys(tableSchema.primaryKey).length === 0)
|
|
1426
|
+
if (Object.keys(tableSchema.primaryKey).length === 0) {
|
|
1427
|
+
const log = loggerConfig?.enabled ? loggerConfig.logger : void 0;
|
|
1428
|
+
const msg = `Could not find primary key for ${table.toString()}. Cannot register subscriptions!`;
|
|
1429
|
+
log ? log.warn({ "rumble.table": table.toString() }, msg) : console.warn(msg);
|
|
1430
|
+
}
|
|
1243
1431
|
const primaryKey = Object.values(tableSchema.primaryKey)[0];
|
|
1244
1432
|
const { registerOnInstance } = makePubSubInstance({ table });
|
|
1245
1433
|
return schemaBuilder.drizzleObject(table, {
|
|
@@ -1248,7 +1436,9 @@ const createObjectImplementer = ({ db, search, schemaBuilder, makePubSubInstance
|
|
|
1248
1436
|
if (!primaryKey) return;
|
|
1249
1437
|
const primaryKeyValue = element[primaryKey.name];
|
|
1250
1438
|
if (!primaryKeyValue) {
|
|
1251
|
-
|
|
1439
|
+
const log = loggerConfig?.enabled ? loggerConfig.logger : void 0;
|
|
1440
|
+
const msg = `Could not find primary key value for element on ${table.toString()}. Cannot register subscription!`;
|
|
1441
|
+
log ? log.warn({ "rumble.table": table.toString() }, msg) : console.warn(msg);
|
|
1252
1442
|
return;
|
|
1253
1443
|
}
|
|
1254
1444
|
registerOnInstance({
|
|
@@ -1279,6 +1469,10 @@ const createObjectImplementer = ({ db, search, schemaBuilder, makePubSubInstance
|
|
|
1279
1469
|
};
|
|
1280
1470
|
} })) ?? {};
|
|
1281
1471
|
const fields = Object.entries(columns).reduce((acc, [key, column]) => {
|
|
1472
|
+
if (key.startsWith("__")) {
|
|
1473
|
+
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.`);
|
|
1474
|
+
return acc;
|
|
1475
|
+
}
|
|
1282
1476
|
if (userAdjustments[key]) {
|
|
1283
1477
|
const { params, creatorFunction, configObject } = configMap.get(userAdjustments[key]);
|
|
1284
1478
|
if (typeof configObject.nullable !== "boolean") configObject.nullable = !column.notNull;
|
|
@@ -1479,7 +1673,8 @@ const createPubSubInstance = ({ subscriptions }) => {
|
|
|
1479
1673
|
};
|
|
1480
1674
|
//#endregion
|
|
1481
1675
|
//#region lib/query.ts
|
|
1482
|
-
const createQueryImplementer = ({ db, schemaBuilder, search, whereArgImplementer, orderArgImplementer, makePubSubInstance }) => {
|
|
1676
|
+
const createQueryImplementer = ({ db, schemaBuilder, search, logger: loggerConfig, whereArgImplementer, orderArgImplementer, makePubSubInstance }) => {
|
|
1677
|
+
const registeredQueryFieldNames = /* @__PURE__ */ new Set();
|
|
1483
1678
|
return ({ table, readAction = "read", listAction = "read" }) => {
|
|
1484
1679
|
const WhereArg = whereArgImplementer({ table });
|
|
1485
1680
|
const OrderArg = orderArgImplementer({ table });
|
|
@@ -1489,6 +1684,12 @@ const createQueryImplementer = ({ db, schemaBuilder, search, whereArgImplementer
|
|
|
1489
1684
|
});
|
|
1490
1685
|
const primaryKeyField = Object.values(tableSchema.primaryKey)[0];
|
|
1491
1686
|
const { registerOnInstance } = makePubSubInstance({ table });
|
|
1687
|
+
const pluralName = pluralize.plural(table.toString());
|
|
1688
|
+
const singularName = pluralize.singular(table.toString());
|
|
1689
|
+
for (const name of [pluralName, singularName]) {
|
|
1690
|
+
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").`);
|
|
1691
|
+
registeredQueryFieldNames.add(name);
|
|
1692
|
+
}
|
|
1492
1693
|
return schemaBuilder.queryFields((t) => {
|
|
1493
1694
|
const manyArgs = {
|
|
1494
1695
|
where: t.arg({
|
|
@@ -1505,11 +1706,11 @@ const createQueryImplementer = ({ db, schemaBuilder, search, whereArgImplementer
|
|
|
1505
1706
|
};
|
|
1506
1707
|
if (!search?.enabled) delete manyArgs.search;
|
|
1507
1708
|
return {
|
|
1508
|
-
[
|
|
1709
|
+
[pluralName]: t.drizzleField({
|
|
1509
1710
|
type: [table],
|
|
1510
1711
|
nullable: false,
|
|
1511
1712
|
smartSubscription: true,
|
|
1512
|
-
description: `List all ${
|
|
1713
|
+
description: `List all ${pluralName}`,
|
|
1513
1714
|
subscribe: (subscriptions, _root, _args, _ctx, _info) => {
|
|
1514
1715
|
registerOnInstance({
|
|
1515
1716
|
instance: subscriptions,
|
|
@@ -1537,17 +1738,21 @@ const createQueryImplementer = ({ db, schemaBuilder, search, whereArgImplementer
|
|
|
1537
1738
|
if (filter.columns) queryInstance.columns = filter.columns;
|
|
1538
1739
|
if (search?.enabled && args.search && args.search.length > 0 && search?.cpu_operator_cost) return db.transaction(async (tx) => {
|
|
1539
1740
|
if (isPostgresDB(tx)) await tx.execute(sql.raw(`SET cpu_operator_cost = ${search.cpu_operator_cost};`));
|
|
1540
|
-
else
|
|
1741
|
+
else {
|
|
1742
|
+
const msg = "Database dialect is not postgresql, cannot set cpu_operator_cost.";
|
|
1743
|
+
const log = loggerConfig?.enabled ? loggerConfig.logger : void 0;
|
|
1744
|
+
log ? log.info({}, msg) : console.info(msg);
|
|
1745
|
+
}
|
|
1541
1746
|
return tx.query[table].findMany(queryInstance);
|
|
1542
1747
|
});
|
|
1543
1748
|
return db.query[table].findMany(queryInstance);
|
|
1544
1749
|
}
|
|
1545
1750
|
}),
|
|
1546
|
-
[
|
|
1751
|
+
[singularName]: t.drizzleField({
|
|
1547
1752
|
type: table,
|
|
1548
1753
|
nullable: false,
|
|
1549
1754
|
smartSubscription: true,
|
|
1550
|
-
description: `Get a single ${
|
|
1755
|
+
description: `Get a single ${singularName} by ID`,
|
|
1551
1756
|
args: { id: t.arg.id({ required: true }) },
|
|
1552
1757
|
resolve: (query, _root, args, ctx, _info) => {
|
|
1553
1758
|
deepSetProto(args);
|
|
@@ -1723,6 +1928,96 @@ function implementDefaultWhereInputArgs(schemaBuilder) {
|
|
|
1723
1928
|
OR: t.field({ type: [JSONWhereInputArgument] }),
|
|
1724
1929
|
NOT: t.field({ type: JSONWhereInputArgument })
|
|
1725
1930
|
}) });
|
|
1931
|
+
const BigIntWhereInputArgument = schemaBuilder.inputRef("BigIntWhereInputArgument").implement({ fields: (t) => ({
|
|
1932
|
+
eq: t.field({
|
|
1933
|
+
type: "BigInt",
|
|
1934
|
+
required: false
|
|
1935
|
+
}),
|
|
1936
|
+
ne: t.field({
|
|
1937
|
+
type: "BigInt",
|
|
1938
|
+
required: false
|
|
1939
|
+
}),
|
|
1940
|
+
gt: t.field({
|
|
1941
|
+
type: "BigInt",
|
|
1942
|
+
required: false
|
|
1943
|
+
}),
|
|
1944
|
+
gte: t.field({
|
|
1945
|
+
type: "BigInt",
|
|
1946
|
+
required: false
|
|
1947
|
+
}),
|
|
1948
|
+
lt: t.field({
|
|
1949
|
+
type: "BigInt",
|
|
1950
|
+
required: false
|
|
1951
|
+
}),
|
|
1952
|
+
lte: t.field({
|
|
1953
|
+
type: "BigInt",
|
|
1954
|
+
required: false
|
|
1955
|
+
}),
|
|
1956
|
+
in: t.field({
|
|
1957
|
+
type: ["BigInt"],
|
|
1958
|
+
required: false
|
|
1959
|
+
}),
|
|
1960
|
+
notIn: t.field({
|
|
1961
|
+
type: ["BigInt"],
|
|
1962
|
+
required: false
|
|
1963
|
+
}),
|
|
1964
|
+
isNull: t.boolean(),
|
|
1965
|
+
isNotNull: t.boolean(),
|
|
1966
|
+
AND: t.field({ type: [BigIntWhereInputArgument] }),
|
|
1967
|
+
OR: t.field({ type: [BigIntWhereInputArgument] }),
|
|
1968
|
+
NOT: t.field({ type: BigIntWhereInputArgument })
|
|
1969
|
+
}) });
|
|
1970
|
+
const DateTimeWhereInputArgument = schemaBuilder.inputRef("DateTimeWhereInputArgument").implement({ fields: (t) => ({
|
|
1971
|
+
eq: t.field({
|
|
1972
|
+
type: "DateTime",
|
|
1973
|
+
required: false
|
|
1974
|
+
}),
|
|
1975
|
+
ne: t.field({
|
|
1976
|
+
type: "DateTime",
|
|
1977
|
+
required: false
|
|
1978
|
+
}),
|
|
1979
|
+
gt: t.field({
|
|
1980
|
+
type: "DateTime",
|
|
1981
|
+
required: false
|
|
1982
|
+
}),
|
|
1983
|
+
gte: t.field({
|
|
1984
|
+
type: "DateTime",
|
|
1985
|
+
required: false
|
|
1986
|
+
}),
|
|
1987
|
+
lt: t.field({
|
|
1988
|
+
type: "DateTime",
|
|
1989
|
+
required: false
|
|
1990
|
+
}),
|
|
1991
|
+
lte: t.field({
|
|
1992
|
+
type: "DateTime",
|
|
1993
|
+
required: false
|
|
1994
|
+
}),
|
|
1995
|
+
in: t.field({
|
|
1996
|
+
type: ["DateTime"],
|
|
1997
|
+
required: false
|
|
1998
|
+
}),
|
|
1999
|
+
notIn: t.field({
|
|
2000
|
+
type: ["DateTime"],
|
|
2001
|
+
required: false
|
|
2002
|
+
}),
|
|
2003
|
+
isNull: t.boolean(),
|
|
2004
|
+
isNotNull: t.boolean(),
|
|
2005
|
+
arrayOverlaps: t.field({
|
|
2006
|
+
type: ["DateTime"],
|
|
2007
|
+
required: false
|
|
2008
|
+
}),
|
|
2009
|
+
arrayContained: t.field({
|
|
2010
|
+
type: ["DateTime"],
|
|
2011
|
+
required: false
|
|
2012
|
+
}),
|
|
2013
|
+
arrayContains: t.field({
|
|
2014
|
+
type: ["DateTime"],
|
|
2015
|
+
required: false
|
|
2016
|
+
}),
|
|
2017
|
+
AND: t.field({ type: [DateTimeWhereInputArgument] }),
|
|
2018
|
+
OR: t.field({ type: [DateTimeWhereInputArgument] }),
|
|
2019
|
+
NOT: t.field({ type: DateTimeWhereInputArgument })
|
|
2020
|
+
}) });
|
|
1726
2021
|
}
|
|
1727
2022
|
//#endregion
|
|
1728
2023
|
//#region lib/runtimeFiltersPlugin/filterTypes.ts
|
|
@@ -1733,9 +2028,11 @@ const applyFiltersKey = "applyFilters";
|
|
|
1733
2028
|
var RuntimeFiltersPlugin = class extends BasePlugin {
|
|
1734
2029
|
tracer;
|
|
1735
2030
|
tracerEnabled;
|
|
2031
|
+
logger;
|
|
1736
2032
|
onTypeConfig(typeConfig) {
|
|
1737
2033
|
this.tracer = this.builder.options.otel?.tracer;
|
|
1738
2034
|
this.tracerEnabled = this.builder.options.otel?.enabled;
|
|
2035
|
+
this.logger = this.builder.options.logger?.enabled ? this.builder.options.logger.logger : void 0;
|
|
1739
2036
|
return typeConfig;
|
|
1740
2037
|
}
|
|
1741
2038
|
wrapResolve(resolver, fieldConfig) {
|
|
@@ -1764,7 +2061,8 @@ var RuntimeFiltersPlugin = class extends BasePlugin {
|
|
|
1764
2061
|
}));
|
|
1765
2062
|
let resolved, prefetchedFilters;
|
|
1766
2063
|
if (this.tracer && this.tracerEnabled) {
|
|
1767
|
-
const o = await this.tracer.startActiveSpan(`
|
|
2064
|
+
const o = await this.tracer.startActiveSpan(`rumble.filter.resolve`, async (span) => {
|
|
2065
|
+
span.setAttribute("graphql.field.name", fieldConfig.name);
|
|
1768
2066
|
try {
|
|
1769
2067
|
return await Promise.all([resolver(parent, args, context, info), prefetchedFiltersPromises]);
|
|
1770
2068
|
} finally {
|
|
@@ -1786,17 +2084,37 @@ var RuntimeFiltersPlugin = class extends BasePlugin {
|
|
|
1786
2084
|
return acc;
|
|
1787
2085
|
}, /* @__PURE__ */ new Set()));
|
|
1788
2086
|
span?.setAttribute("filters.allowed", allowed.length);
|
|
2087
|
+
this.logger?.debug({
|
|
2088
|
+
"graphql.field.name": fieldConfig.name,
|
|
2089
|
+
"filters.total": allFilters.length,
|
|
2090
|
+
"filters.allowed": allowed.length
|
|
2091
|
+
}, "runtime filters applied");
|
|
1789
2092
|
if (Array.isArray(resolved)) return allowed;
|
|
1790
2093
|
return allowed[0] ?? null;
|
|
1791
2094
|
};
|
|
1792
|
-
if (this.tracer && this.tracerEnabled) return this.tracer.startActiveSpan(`
|
|
2095
|
+
if (this.tracer && this.tracerEnabled) return this.tracer.startActiveSpan(`rumble.filter.apply`, async (span) => {
|
|
2096
|
+
span.setAttribute("graphql.field.name", fieldConfig.name);
|
|
1793
2097
|
try {
|
|
1794
2098
|
return await runFilters(span);
|
|
2099
|
+
} catch (error) {
|
|
2100
|
+
this.logger?.error({
|
|
2101
|
+
"graphql.field.name": fieldConfig.name,
|
|
2102
|
+
err: error
|
|
2103
|
+
}, "runtime filter threw");
|
|
2104
|
+
throw error;
|
|
1795
2105
|
} finally {
|
|
1796
2106
|
span.end();
|
|
1797
2107
|
}
|
|
1798
2108
|
});
|
|
1799
|
-
else
|
|
2109
|
+
else try {
|
|
2110
|
+
return await runFilters();
|
|
2111
|
+
} catch (error) {
|
|
2112
|
+
this.logger?.error({
|
|
2113
|
+
"graphql.field.name": fieldConfig.name,
|
|
2114
|
+
err: error
|
|
2115
|
+
}, "runtime filter threw");
|
|
2116
|
+
throw error;
|
|
2117
|
+
}
|
|
1800
2118
|
};
|
|
1801
2119
|
}
|
|
1802
2120
|
};
|
|
@@ -1809,7 +2127,7 @@ function registerRuntimeFiltersPlugin() {
|
|
|
1809
2127
|
}
|
|
1810
2128
|
//#endregion
|
|
1811
2129
|
//#region lib/schemaBuilder.ts
|
|
1812
|
-
const createSchemaBuilder = ({ db,
|
|
2130
|
+
const createSchemaBuilder = ({ db, pubsub, pothosConfig, otel, logger }) => {
|
|
1813
2131
|
const createSpan = otel?.enabled && otel.tracer ? createOpenTelemetryWrapper(otel.tracer, otel.options) : void 0;
|
|
1814
2132
|
registerRuntimeFiltersPlugin();
|
|
1815
2133
|
const schemaBuilder = new SchemaBuilder({
|
|
@@ -1837,18 +2155,40 @@ const createSchemaBuilder = ({ db, disableDefaultObjects, pubsub, pothosConfig,
|
|
|
1837
2155
|
}) },
|
|
1838
2156
|
defaultFieldNullability: false,
|
|
1839
2157
|
tracing: {
|
|
1840
|
-
default: otel?.enabled ? (config) => isRootField(config) : () => false,
|
|
1841
|
-
wrap:
|
|
2158
|
+
default: otel?.enabled || logger?.enabled ? (config) => isRootField(config) : () => false,
|
|
2159
|
+
wrap: (resolver, options, config) => {
|
|
2160
|
+
let r = createSpan ? createSpan(resolver, options) : resolver;
|
|
2161
|
+
if (logger?.enabled) {
|
|
2162
|
+
const log = logger.logger;
|
|
2163
|
+
r = wrapResolver(r, (error, duration) => {
|
|
2164
|
+
if (error) log.error({
|
|
2165
|
+
"graphql.field.name": config.name,
|
|
2166
|
+
"graphql.parent.type": config.parentType,
|
|
2167
|
+
durationMs: duration,
|
|
2168
|
+
err: error
|
|
2169
|
+
}, "resolver failed");
|
|
2170
|
+
else log.debug({
|
|
2171
|
+
"graphql.field.name": config.name,
|
|
2172
|
+
"graphql.parent.type": config.parentType,
|
|
2173
|
+
durationMs: duration
|
|
2174
|
+
}, "resolver completed");
|
|
2175
|
+
});
|
|
2176
|
+
}
|
|
2177
|
+
return r;
|
|
2178
|
+
}
|
|
1842
2179
|
},
|
|
1843
|
-
otel
|
|
2180
|
+
otel,
|
|
2181
|
+
logger
|
|
1844
2182
|
});
|
|
1845
2183
|
schemaBuilder.addScalarType("JSON", JSONResolver);
|
|
1846
2184
|
schemaBuilder.addScalarType("Date", DateResolver);
|
|
1847
2185
|
schemaBuilder.addScalarType("DateTime", DateTimeISOResolver);
|
|
2186
|
+
schemaBuilder.addScalarType("BigInt", BigIntResolver);
|
|
2187
|
+
schemaBuilder.addScalarType("Bytes", ByteResolver);
|
|
1848
2188
|
implementDefaultWhereInputArgs(schemaBuilder);
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
2189
|
+
schemaBuilder.queryType({});
|
|
2190
|
+
schemaBuilder.subscriptionType({});
|
|
2191
|
+
schemaBuilder.mutationType({});
|
|
1852
2192
|
return { schemaBuilder };
|
|
1853
2193
|
};
|
|
1854
2194
|
//#endregion
|
|
@@ -1931,7 +2271,33 @@ export const r = rumble({
|
|
|
1931
2271
|
whereArgImplementer: whereArg,
|
|
1932
2272
|
makePubSubInstance
|
|
1933
2273
|
});
|
|
1934
|
-
const builtSchema = lazy(() =>
|
|
2274
|
+
const builtSchema = lazy(() => {
|
|
2275
|
+
let raw;
|
|
2276
|
+
try {
|
|
2277
|
+
raw = schemaBuilder.toSchema();
|
|
2278
|
+
} catch (e) {
|
|
2279
|
+
const unimplMatch = (e?.message ?? String(e)).match(/ObjectRef<(.+?)> has not been implemented/);
|
|
2280
|
+
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.`);
|
|
2281
|
+
throw e;
|
|
2282
|
+
}
|
|
2283
|
+
const mutation = raw.getMutationType();
|
|
2284
|
+
const subscription = raw.getSubscriptionType();
|
|
2285
|
+
const emptyMutation = mutation && Object.keys(mutation.getFields()).length === 0;
|
|
2286
|
+
const emptySubscription = subscription && Object.keys(subscription.getFields()).length === 0;
|
|
2287
|
+
if (!emptyMutation && !emptySubscription) return raw;
|
|
2288
|
+
const config = raw.toConfig();
|
|
2289
|
+
const filteredTypes = config.types.filter((t) => {
|
|
2290
|
+
if (emptyMutation && t === mutation) return false;
|
|
2291
|
+
if (emptySubscription && t === subscription) return false;
|
|
2292
|
+
return true;
|
|
2293
|
+
});
|
|
2294
|
+
return new GraphQLSchema({
|
|
2295
|
+
...config,
|
|
2296
|
+
mutation: emptyMutation ? void 0 : config.mutation,
|
|
2297
|
+
subscription: emptySubscription ? void 0 : config.subscription,
|
|
2298
|
+
types: filteredTypes
|
|
2299
|
+
});
|
|
2300
|
+
});
|
|
1935
2301
|
const createYoga$1 = (args) => {
|
|
1936
2302
|
const enableApiDocs = args?.enableApiDocs ?? process?.env?.NODE_ENV === "development";
|
|
1937
2303
|
return createYoga({
|
|
@@ -1942,26 +2308,60 @@ export const r = rumble({
|
|
|
1942
2308
|
plugins: [
|
|
1943
2309
|
...args?.plugins ?? [],
|
|
1944
2310
|
...enableApiDocs ? [] : [useDisableIntrospection(), EnvelopArmorPlugin(args?.armorConfig)],
|
|
1945
|
-
rumbleInput.otel?.enabled ? { onExecute: ({ setExecuteFn, executeFn }) => {
|
|
1946
|
-
setExecuteFn((options) =>
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
2311
|
+
rumbleInput.otel?.enabled || rumbleInput.logger?.enabled ? { onExecute: ({ setExecuteFn, executeFn }) => {
|
|
2312
|
+
setExecuteFn(async (options) => {
|
|
2313
|
+
const log = rumbleInput.logger?.enabled ? rumbleInput.logger.logger : void 0;
|
|
2314
|
+
const operationName = options.operationName ?? "anonymous";
|
|
2315
|
+
const start = Date.now();
|
|
2316
|
+
log?.info({ "graphql.operation.name": operationName }, "graphql execute start");
|
|
2317
|
+
const run = async () => {
|
|
1951
2318
|
const result = await executeFn(options);
|
|
1952
|
-
if (result && "errors" in result && result.errors?.length) {
|
|
1953
|
-
|
|
2319
|
+
if (result && "errors" in result && result.errors?.length) log?.error({
|
|
2320
|
+
"graphql.operation.name": operationName,
|
|
2321
|
+
durationMs: Date.now() - start,
|
|
2322
|
+
errors: result.errors.map((e) => e.message)
|
|
2323
|
+
}, "graphql execute completed with errors");
|
|
2324
|
+
else log?.info({
|
|
2325
|
+
"graphql.operation.name": operationName,
|
|
2326
|
+
durationMs: Date.now() - start
|
|
2327
|
+
}, "graphql execute completed");
|
|
2328
|
+
return result;
|
|
2329
|
+
};
|
|
2330
|
+
if (rumbleInput.otel?.enabled) return rumbleInput.otel.tracer.startActiveSpan(SpanNames.EXECUTE, { attributes: {
|
|
2331
|
+
[AttributeNames.OPERATION_NAME]: operationName,
|
|
2332
|
+
[AttributeNames.SOURCE]: options.document
|
|
2333
|
+
} }, async (span) => {
|
|
2334
|
+
try {
|
|
2335
|
+
const result = await run();
|
|
2336
|
+
if (result && "errors" in result && result.errors?.length) {
|
|
2337
|
+
for (const error of result.errors) span.recordException(error);
|
|
2338
|
+
span.setStatus({ code: SpanStatusCode.ERROR });
|
|
2339
|
+
}
|
|
2340
|
+
return result;
|
|
2341
|
+
} catch (error) {
|
|
2342
|
+
if (error instanceof Error) span.recordException(error);
|
|
2343
|
+
log?.error({
|
|
2344
|
+
"graphql.operation.name": operationName,
|
|
2345
|
+
durationMs: Date.now() - start,
|
|
2346
|
+
err: error
|
|
2347
|
+
}, "graphql execute threw");
|
|
1954
2348
|
span.setStatus({ code: SpanStatusCode.ERROR });
|
|
2349
|
+
throw error;
|
|
2350
|
+
} finally {
|
|
2351
|
+
span.end();
|
|
1955
2352
|
}
|
|
1956
|
-
|
|
2353
|
+
});
|
|
2354
|
+
try {
|
|
2355
|
+
return await run();
|
|
1957
2356
|
} catch (error) {
|
|
1958
|
-
|
|
1959
|
-
|
|
2357
|
+
log?.error({
|
|
2358
|
+
"graphql.operation.name": operationName,
|
|
2359
|
+
durationMs: Date.now() - start,
|
|
2360
|
+
err: error
|
|
2361
|
+
}, "graphql execute threw");
|
|
1960
2362
|
throw error;
|
|
1961
|
-
} finally {
|
|
1962
|
-
span.end();
|
|
1963
2363
|
}
|
|
1964
|
-
})
|
|
2364
|
+
});
|
|
1965
2365
|
} } : false
|
|
1966
2366
|
].filter(Boolean)
|
|
1967
2367
|
});
|