@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.cjs
CHANGED
|
@@ -33,9 +33,9 @@ let graphql_yoga = require("graphql-yoga");
|
|
|
33
33
|
let drizzle_orm = require("drizzle-orm");
|
|
34
34
|
let drizzle_orm_casing = require("drizzle-orm/casing");
|
|
35
35
|
let drizzle_orm_pg_core = require("drizzle-orm/pg-core");
|
|
36
|
+
let drizzle_orm_mysql_core = require("drizzle-orm/mysql-core");
|
|
36
37
|
let pluralize = require("pluralize");
|
|
37
38
|
pluralize = __toESM(pluralize, 1);
|
|
38
|
-
let drizzle_orm_mysql_core = require("drizzle-orm/mysql-core");
|
|
39
39
|
let drizzle_orm_sqlite_core = require("drizzle-orm/sqlite-core");
|
|
40
40
|
let _pothos_core = require("@pothos/core");
|
|
41
41
|
_pothos_core = __toESM(_pothos_core, 1);
|
|
@@ -208,7 +208,7 @@ function mapNullFieldsToUndefined(obj) {
|
|
|
208
208
|
}
|
|
209
209
|
//#endregion
|
|
210
210
|
//#region package.json
|
|
211
|
-
var version = "0.
|
|
211
|
+
var version = "0.23.2";
|
|
212
212
|
//#endregion
|
|
213
213
|
//#region lib/helpers/mergeFilters.ts
|
|
214
214
|
function mergeFilters(filterA, filterB, mode = "AND") {
|
|
@@ -227,23 +227,47 @@ function mergeFilters(filterA, filterB, mode = "AND") {
|
|
|
227
227
|
}
|
|
228
228
|
//#endregion
|
|
229
229
|
//#region lib/helpers/sqlTypes/types.ts
|
|
230
|
+
/**
|
|
231
|
+
* Strips parameter suffixes like `(256)`, `(10,2)` and timezone modifiers
|
|
232
|
+
* (`with time zone`, `without time zone`) from raw getSQLType() output so
|
|
233
|
+
* every classifier works on the base type name only.
|
|
234
|
+
*/
|
|
235
|
+
function normalizeSQLType(raw) {
|
|
236
|
+
return raw.replace(/\s*with(?:out)?\s+time\s+zone/i, "").replace(/\s*\([^)]*\)/, "").trim().toLowerCase();
|
|
237
|
+
}
|
|
230
238
|
const intLikeSQLTypeStrings = [
|
|
231
239
|
"serial",
|
|
232
240
|
"int",
|
|
233
241
|
"integer",
|
|
234
242
|
"tinyint",
|
|
235
243
|
"smallint",
|
|
236
|
-
"mediumint"
|
|
244
|
+
"mediumint",
|
|
245
|
+
"int2",
|
|
246
|
+
"int4",
|
|
247
|
+
"smallserial"
|
|
237
248
|
];
|
|
238
249
|
function isIntLikeSQLTypeString(sqlType) {
|
|
239
250
|
return intLikeSQLTypeStrings.includes(sqlType);
|
|
240
251
|
}
|
|
252
|
+
const bigIntLikeSQLTypeStrings = [
|
|
253
|
+
"bigint",
|
|
254
|
+
"bigserial",
|
|
255
|
+
"int8",
|
|
256
|
+
"bigint unsigned"
|
|
257
|
+
];
|
|
258
|
+
function isBigIntLikeSQLTypeString(sqlType) {
|
|
259
|
+
return bigIntLikeSQLTypeStrings.includes(sqlType);
|
|
260
|
+
}
|
|
241
261
|
const floatLikeSQLTypeStrings = [
|
|
242
262
|
"real",
|
|
243
263
|
"decimal",
|
|
244
264
|
"double",
|
|
245
265
|
"float",
|
|
246
|
-
"numeric"
|
|
266
|
+
"numeric",
|
|
267
|
+
"double precision",
|
|
268
|
+
"float4",
|
|
269
|
+
"float8",
|
|
270
|
+
"money"
|
|
247
271
|
];
|
|
248
272
|
function isFloatLikeSQLTypeString(sqlType) {
|
|
249
273
|
return floatLikeSQLTypeStrings.includes(sqlType);
|
|
@@ -253,7 +277,20 @@ const stringLikeSQLTypeStrings = [
|
|
|
253
277
|
"text",
|
|
254
278
|
"varchar",
|
|
255
279
|
"char",
|
|
256
|
-
"
|
|
280
|
+
"character varying",
|
|
281
|
+
"character",
|
|
282
|
+
"inet",
|
|
283
|
+
"cidr",
|
|
284
|
+
"macaddr",
|
|
285
|
+
"macaddr8",
|
|
286
|
+
"interval",
|
|
287
|
+
"time",
|
|
288
|
+
"year",
|
|
289
|
+
"binary",
|
|
290
|
+
"varbinary",
|
|
291
|
+
"tinytext",
|
|
292
|
+
"mediumtext",
|
|
293
|
+
"longtext"
|
|
257
294
|
];
|
|
258
295
|
function isStringLikeSQLTypeString(sqlType) {
|
|
259
296
|
return stringLikeSQLTypeStrings.includes(sqlType);
|
|
@@ -262,7 +299,7 @@ const IDLikeSQLTypeStrings = ["uuid"];
|
|
|
262
299
|
function isIDLikeSQLTypeString(sqlType) {
|
|
263
300
|
return IDLikeSQLTypeStrings.includes(sqlType);
|
|
264
301
|
}
|
|
265
|
-
const booleanSQLTypeStrings = ["boolean"];
|
|
302
|
+
const booleanSQLTypeStrings = ["boolean", "bool"];
|
|
266
303
|
function isBooleanSQLTypeString(sqlType) {
|
|
267
304
|
return booleanSQLTypeStrings.includes(sqlType);
|
|
268
305
|
}
|
|
@@ -278,16 +315,23 @@ const jsonLikeSQLTypeStrings = ["json", "jsonb"];
|
|
|
278
315
|
function isJSONLikeSQLTypeString(sqlType) {
|
|
279
316
|
return jsonLikeSQLTypeStrings.includes(sqlType);
|
|
280
317
|
}
|
|
281
|
-
[
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
318
|
+
const jsonFallbackSQLTypeStrings = [
|
|
319
|
+
"point",
|
|
320
|
+
"line",
|
|
321
|
+
"lseg",
|
|
322
|
+
"box",
|
|
323
|
+
"path",
|
|
324
|
+
"polygon",
|
|
325
|
+
"circle",
|
|
326
|
+
"geometry"
|
|
290
327
|
];
|
|
328
|
+
function isJSONFallbackSQLTypeString(sqlType) {
|
|
329
|
+
return jsonFallbackSQLTypeStrings.includes(sqlType) || sqlType.startsWith("geometry");
|
|
330
|
+
}
|
|
331
|
+
const bytesSQLTypeStrings = ["bytea", "blob"];
|
|
332
|
+
function isBytesSQLTypeString(sqlType) {
|
|
333
|
+
return bytesSQLTypeStrings.includes(sqlType);
|
|
334
|
+
}
|
|
291
335
|
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})`);
|
|
292
336
|
//#endregion
|
|
293
337
|
//#region lib/helpers/sqlTypes/distinctValuesFromSQLType.ts
|
|
@@ -402,16 +446,17 @@ function isDynamicQueryFilter(filter) {
|
|
|
402
446
|
function isStaticQueryFilter(filter) {
|
|
403
447
|
return typeof filter !== "function";
|
|
404
448
|
}
|
|
405
|
-
const
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
`);
|
|
449
|
+
const makeNothingRegisteredWarner = (logger) => (0, es_toolkit.debounce)((model, action) => {
|
|
450
|
+
const msg = `No abilities registered for ${model}/${action} — blocking everything. Register the ability or ignore this warning if intentional.`;
|
|
451
|
+
if (logger) logger.warn({
|
|
452
|
+
"rumble.table": model,
|
|
453
|
+
"rumble.action": action
|
|
454
|
+
}, msg);
|
|
455
|
+
else console.warn(msg);
|
|
413
456
|
}, 1e3);
|
|
414
|
-
const createAbilityBuilder = ({ db, actions, defaultLimit, otel }) => {
|
|
457
|
+
const createAbilityBuilder = ({ db, actions, defaultLimit, otel, logger: loggerConfig }) => {
|
|
458
|
+
const log = loggerConfig?.enabled ? loggerConfig.logger : void 0;
|
|
459
|
+
const nothingRegisteredWarningLogger = makeNothingRegisteredWarner(log);
|
|
415
460
|
let hasBeenBuilt = false;
|
|
416
461
|
const createBuilderForTable = () => {
|
|
417
462
|
const queryFilters = /* @__PURE__ */ new Map();
|
|
@@ -659,7 +704,8 @@ by: (explicitFilter) => {
|
|
|
659
704
|
* Merges the current query filters with the provided filters for this call only
|
|
660
705
|
*/
|
|
661
706
|
function merge(p) {
|
|
662
|
-
|
|
707
|
+
const merged = mergeFilters(ret.query.many, p);
|
|
708
|
+
return internalTransformer(merged, p.limit);
|
|
663
709
|
}
|
|
664
710
|
ret.merge = merge;
|
|
665
711
|
return ret;
|
|
@@ -701,14 +747,39 @@ by: (explicitFilter) => {
|
|
|
701
747
|
span?.setAttribute("abilities.status", "applied");
|
|
702
748
|
return transformToResponse(mergedFilters);
|
|
703
749
|
};
|
|
704
|
-
if (otel?.enabled && otel.tracer) return otel.tracer.startActiveSpan(`
|
|
750
|
+
if (otel?.enabled && otel.tracer) return otel.tracer.startActiveSpan(`rumble.abilities.prepare`, (span) => {
|
|
751
|
+
span.setAttribute("rumble.action", action);
|
|
752
|
+
span.setAttribute("rumble.table", tableName.toString());
|
|
705
753
|
try {
|
|
706
|
-
|
|
754
|
+
const attrs = {
|
|
755
|
+
"rumble.table": tableName.toString(),
|
|
756
|
+
"rumble.action": action
|
|
757
|
+
};
|
|
758
|
+
const proxy = new Proxy(span, { get(target, prop) {
|
|
759
|
+
if (prop === "setAttribute") return (k, v) => {
|
|
760
|
+
attrs[k] = v;
|
|
761
|
+
return target.setAttribute(k, v);
|
|
762
|
+
};
|
|
763
|
+
return target[prop];
|
|
764
|
+
} });
|
|
765
|
+
const result = assembleAbilities(proxy);
|
|
766
|
+
log?.debug(attrs, "abilities prepared");
|
|
767
|
+
return result;
|
|
707
768
|
} finally {
|
|
708
769
|
span.end();
|
|
709
770
|
}
|
|
710
771
|
});
|
|
711
|
-
else
|
|
772
|
+
else {
|
|
773
|
+
const attrs = {
|
|
774
|
+
"rumble.table": tableName.toString(),
|
|
775
|
+
"rumble.action": action
|
|
776
|
+
};
|
|
777
|
+
const result = assembleAbilities(log ? { setAttribute: (k, v) => {
|
|
778
|
+
attrs[k] = v;
|
|
779
|
+
} } : void 0);
|
|
780
|
+
log?.debug(attrs, "abilities prepared");
|
|
781
|
+
return result;
|
|
782
|
+
}
|
|
712
783
|
} };
|
|
713
784
|
} };
|
|
714
785
|
};
|
|
@@ -737,29 +808,14 @@ const createOrderArgImplementer = ({ db, schemaBuilder }) => {
|
|
|
737
808
|
if (ret) return ret;
|
|
738
809
|
const implement = () => {
|
|
739
810
|
return schemaBuilder.inputType(inputTypeName, { fields: (t) => {
|
|
740
|
-
|
|
811
|
+
return Object.entries(tableSchema.columns).reduce((acc, [key]) => {
|
|
812
|
+
if (key.startsWith("__")) return acc;
|
|
741
813
|
acc[key] = t.field({
|
|
742
814
|
type: sortingParameterEnumRef(),
|
|
743
815
|
required: false
|
|
744
816
|
});
|
|
745
817
|
return acc;
|
|
746
818
|
}, {});
|
|
747
|
-
const relations = Object.entries(tableSchema.relations ?? {}).reduce((acc, [key, value]) => {
|
|
748
|
-
const targetTsName = value.targetTableName;
|
|
749
|
-
const referenceModel = orderArgImplementer({ dbName: tableHelper({
|
|
750
|
-
db,
|
|
751
|
-
table: targetTsName
|
|
752
|
-
}).dbName });
|
|
753
|
-
acc[key] = t.field({
|
|
754
|
-
type: referenceModel,
|
|
755
|
-
required: false
|
|
756
|
-
});
|
|
757
|
-
return acc;
|
|
758
|
-
}, {});
|
|
759
|
-
return {
|
|
760
|
-
...fields,
|
|
761
|
-
...relations
|
|
762
|
-
};
|
|
763
819
|
} });
|
|
764
820
|
};
|
|
765
821
|
ret = implement();
|
|
@@ -771,10 +827,10 @@ const createOrderArgImplementer = ({ db, schemaBuilder }) => {
|
|
|
771
827
|
//#endregion
|
|
772
828
|
//#region lib/enum.ts
|
|
773
829
|
/**
|
|
774
|
-
* Checks if a column is a
|
|
830
|
+
* Checks if a column is a database enum column (Postgres or MySQL).
|
|
775
831
|
*/
|
|
776
832
|
function isEnumSchema(schemaType) {
|
|
777
|
-
return (0, drizzle_orm.is)(schemaType, drizzle_orm_pg_core.PgEnumColumn) || (0, drizzle_orm.is)(schemaType, drizzle_orm_pg_core.PgEnumObjectColumn);
|
|
833
|
+
return (0, drizzle_orm.is)(schemaType, drizzle_orm_pg_core.PgEnumColumn) || (0, drizzle_orm.is)(schemaType, drizzle_orm_pg_core.PgEnumObjectColumn) || (0, drizzle_orm.is)(schemaType, drizzle_orm_mysql_core.MySqlEnumColumn) || (0, drizzle_orm.is)(schemaType, drizzle_orm_mysql_core.MySqlEnumObjectColumn);
|
|
778
834
|
}
|
|
779
835
|
const createEnumImplementer = ({ schema, schemaBuilder }) => {
|
|
780
836
|
const referenceStorage = /* @__PURE__ */ new Map();
|
|
@@ -806,7 +862,12 @@ const createEnumImplementer = ({ schema, schemaBuilder }) => {
|
|
|
806
862
|
if (enumObject) {
|
|
807
863
|
enumSchemaName = enumObject.enumName;
|
|
808
864
|
enumValues = enumObject.enumValues;
|
|
809
|
-
} else if (enumColumn) {
|
|
865
|
+
} else if (enumColumn) if ((0, drizzle_orm.is)(enumColumn, drizzle_orm_mysql_core.MySqlEnumColumn) || (0, drizzle_orm.is)(enumColumn, drizzle_orm_mysql_core.MySqlEnumObjectColumn)) {
|
|
866
|
+
enumValues = enumColumn.enumValues;
|
|
867
|
+
const colTable = enumColumn.table;
|
|
868
|
+
const tableName = colTable && (0, drizzle_orm.isTable)(colTable) ? (0, drizzle_orm.getTableName)(colTable) : void 0;
|
|
869
|
+
enumSchemaName = tableName ? `${tableName}_${enumColumn.name}` : enumColumn.name;
|
|
870
|
+
} else {
|
|
810
871
|
const pgEnum = enumColumn.enum;
|
|
811
872
|
if (!pgEnum) throw new RumbleError(`Could not find enum definition on column. Make sure the column is a pgEnum column.`);
|
|
812
873
|
enumSchemaName = pgEnum.enumName;
|
|
@@ -825,16 +886,21 @@ const createEnumImplementer = ({ schema, schemaBuilder }) => {
|
|
|
825
886
|
//#endregion
|
|
826
887
|
//#region lib/helpers/sqlTypes/mapSQLTypeToTSType.ts
|
|
827
888
|
function mapSQLTypeToGraphQLType({ sqlType, fieldName }) {
|
|
889
|
+
if (/^enum\(/.test(sqlType)) return "String";
|
|
890
|
+
const normalized = normalizeSQLType(sqlType);
|
|
828
891
|
let ret;
|
|
829
|
-
if (isIntLikeSQLTypeString(
|
|
830
|
-
if (
|
|
831
|
-
if (
|
|
892
|
+
if (isIntLikeSQLTypeString(normalized)) ret = "Int";
|
|
893
|
+
if (isBigIntLikeSQLTypeString(normalized)) ret = "BigInt";
|
|
894
|
+
if (isFloatLikeSQLTypeString(normalized)) ret = "Float";
|
|
895
|
+
if (isStringLikeSQLTypeString(normalized)) if (fieldName && (fieldName.toLowerCase().endsWith("_id") || fieldName.toLowerCase().endsWith("id"))) ret = "ID";
|
|
832
896
|
else ret = "String";
|
|
833
|
-
if (isIDLikeSQLTypeString(
|
|
834
|
-
if (isBooleanSQLTypeString(
|
|
835
|
-
if (isDateTimeLikeSQLTypeString(
|
|
836
|
-
if (isDateLikeSQLTypeString(
|
|
837
|
-
if (isJSONLikeSQLTypeString(
|
|
897
|
+
if (isIDLikeSQLTypeString(normalized)) ret = "ID";
|
|
898
|
+
if (isBooleanSQLTypeString(normalized)) ret = "Boolean";
|
|
899
|
+
if (isDateTimeLikeSQLTypeString(normalized)) ret = "DateTime";
|
|
900
|
+
if (isDateLikeSQLTypeString(normalized)) ret = "Date";
|
|
901
|
+
if (isJSONLikeSQLTypeString(normalized)) ret = "JSON";
|
|
902
|
+
if (isJSONFallbackSQLTypeString(normalized)) ret = "JSON";
|
|
903
|
+
if (isBytesSQLTypeString(normalized)) ret = "Bytes";
|
|
838
904
|
if (ret !== void 0) return ret;
|
|
839
905
|
throw UnknownTypeRumbleError(sqlType, "SQL to GQL");
|
|
840
906
|
}
|
|
@@ -844,6 +910,60 @@ const makeDefaultName = (dbName) => `${(0, es_toolkit.capitalize)((0, drizzle_or
|
|
|
844
910
|
const createWhereArgImplementer = ({ db, schemaBuilder, enumImplementer }) => {
|
|
845
911
|
const referenceStorage = /* @__PURE__ */ new Map();
|
|
846
912
|
const enumArrayWhereInputStorage = /* @__PURE__ */ new Map();
|
|
913
|
+
const scalarArrayWhereInputStorage = /* @__PURE__ */ new Map();
|
|
914
|
+
const scalarArrayWhereInputName = (gqlType) => `${gqlType}ArrayWhereInputArgument`;
|
|
915
|
+
const getScalarArrayWhereInput = (gqlType) => {
|
|
916
|
+
const typeName = scalarArrayWhereInputName(gqlType);
|
|
917
|
+
const existing = scalarArrayWhereInputStorage.get(typeName);
|
|
918
|
+
if (existing) return existing;
|
|
919
|
+
const ret = schemaBuilder.inputRef(typeName);
|
|
920
|
+
scalarArrayWhereInputStorage.set(typeName, ret);
|
|
921
|
+
ret.implement({ fields: (t) => ({
|
|
922
|
+
eq: t.field({
|
|
923
|
+
type: [gqlType],
|
|
924
|
+
required: false
|
|
925
|
+
}),
|
|
926
|
+
ne: t.field({
|
|
927
|
+
type: [gqlType],
|
|
928
|
+
required: false
|
|
929
|
+
}),
|
|
930
|
+
in: t.field({
|
|
931
|
+
type: [[gqlType]],
|
|
932
|
+
required: false
|
|
933
|
+
}),
|
|
934
|
+
notIn: t.field({
|
|
935
|
+
type: [[gqlType]],
|
|
936
|
+
required: false
|
|
937
|
+
}),
|
|
938
|
+
isNull: t.boolean({ required: false }),
|
|
939
|
+
isNotNull: t.boolean({ required: false }),
|
|
940
|
+
arrayOverlaps: t.field({
|
|
941
|
+
type: [gqlType],
|
|
942
|
+
required: false
|
|
943
|
+
}),
|
|
944
|
+
arrayContained: t.field({
|
|
945
|
+
type: [gqlType],
|
|
946
|
+
required: false
|
|
947
|
+
}),
|
|
948
|
+
arrayContains: t.field({
|
|
949
|
+
type: [gqlType],
|
|
950
|
+
required: false
|
|
951
|
+
}),
|
|
952
|
+
AND: t.field({
|
|
953
|
+
type: [ret],
|
|
954
|
+
required: false
|
|
955
|
+
}),
|
|
956
|
+
OR: t.field({
|
|
957
|
+
type: [ret],
|
|
958
|
+
required: false
|
|
959
|
+
}),
|
|
960
|
+
NOT: t.field({
|
|
961
|
+
type: ret,
|
|
962
|
+
required: false
|
|
963
|
+
})
|
|
964
|
+
}) });
|
|
965
|
+
return ret;
|
|
966
|
+
};
|
|
847
967
|
const getEnumArrayWhereInput = (enumImpl) => {
|
|
848
968
|
const typeName = `${enumImpl.name}WhereInputArgument`;
|
|
849
969
|
const existing = enumArrayWhereInputStorage.get(typeName);
|
|
@@ -907,39 +1027,82 @@ const createWhereArgImplementer = ({ db, schemaBuilder, enumImplementer }) => {
|
|
|
907
1027
|
const ret = schemaBuilder.inputRef(inputTypeName);
|
|
908
1028
|
referenceStorage.set(inputTypeName, ret);
|
|
909
1029
|
ret.implement({ fields: (t) => {
|
|
910
|
-
const mapSQLTypeStringToInputPothosType = (sqlType, fieldName) => {
|
|
1030
|
+
const mapSQLTypeStringToInputPothosType = (sqlType, fieldName, isArray) => {
|
|
911
1031
|
const gqlType = mapSQLTypeToGraphQLType({
|
|
912
1032
|
sqlType,
|
|
913
1033
|
fieldName
|
|
914
1034
|
});
|
|
1035
|
+
if (isArray) return t.field({
|
|
1036
|
+
type: getScalarArrayWhereInput(gqlType),
|
|
1037
|
+
required: false
|
|
1038
|
+
});
|
|
915
1039
|
switch (gqlType) {
|
|
916
|
-
case "Int": return t.field({
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
case "
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
1040
|
+
case "Int": return t.field({
|
|
1041
|
+
type: "IntWhereInputArgument",
|
|
1042
|
+
required: false
|
|
1043
|
+
});
|
|
1044
|
+
case "BigInt": return t.field({
|
|
1045
|
+
type: "BigIntWhereInputArgument",
|
|
1046
|
+
required: false
|
|
1047
|
+
});
|
|
1048
|
+
case "String": return t.field({
|
|
1049
|
+
type: "StringWhereInputArgument",
|
|
1050
|
+
required: false
|
|
1051
|
+
});
|
|
1052
|
+
case "Boolean": return t.field({
|
|
1053
|
+
type: "BooleanWhereInputArgument",
|
|
1054
|
+
required: false
|
|
1055
|
+
});
|
|
1056
|
+
case "Date": return t.field({
|
|
1057
|
+
type: "DateWhereInputArgument",
|
|
1058
|
+
required: false
|
|
1059
|
+
});
|
|
1060
|
+
case "DateTime": return t.field({
|
|
1061
|
+
type: "DateTimeWhereInputArgument",
|
|
1062
|
+
required: false
|
|
1063
|
+
});
|
|
1064
|
+
case "Float": return t.field({
|
|
1065
|
+
type: "FloatWhereInputArgument",
|
|
1066
|
+
required: false
|
|
1067
|
+
});
|
|
1068
|
+
case "ID": return t.field({
|
|
1069
|
+
type: "IDWhereInputArgument",
|
|
1070
|
+
required: false
|
|
1071
|
+
});
|
|
1072
|
+
case "JSON": return t.field({
|
|
1073
|
+
type: "JSONWhereInputArgument",
|
|
1074
|
+
required: false
|
|
1075
|
+
});
|
|
1076
|
+
case "Bytes": return null;
|
|
924
1077
|
default: throw new RumbleError(`Unsupported argument type ${gqlType} for column ${sqlType}`);
|
|
925
1078
|
}
|
|
926
1079
|
};
|
|
927
1080
|
const fields = Object.entries(tableSchema.columns).reduce((acc, [key, value]) => {
|
|
1081
|
+
if (key.startsWith("__")) return acc;
|
|
1082
|
+
let isArray = false;
|
|
1083
|
+
if ((0, drizzle_orm.is)(value, drizzle_orm_pg_core.PgColumn)) {
|
|
1084
|
+
const dims = value.dimensions;
|
|
1085
|
+
if (dims && dims > 0) isArray = true;
|
|
1086
|
+
}
|
|
928
1087
|
if (isEnumSchema(value)) {
|
|
929
1088
|
const enumImpl = enumImplementer({ enumColumn: value });
|
|
930
1089
|
acc[key] = t.field({
|
|
931
|
-
type:
|
|
1090
|
+
type: isArray ? getEnumArrayWhereInput(enumImpl) : enumImpl,
|
|
932
1091
|
required: false
|
|
933
1092
|
});
|
|
934
|
-
} else
|
|
1093
|
+
} else {
|
|
1094
|
+
const field = mapSQLTypeStringToInputPothosType(value.getSQLType(), key, isArray);
|
|
1095
|
+
if (field !== null) acc[key] = field;
|
|
1096
|
+
}
|
|
935
1097
|
return acc;
|
|
936
1098
|
}, {});
|
|
937
1099
|
const relations = Object.entries(tableSchema.relations ?? {}).reduce((acc, [key, value]) => {
|
|
938
1100
|
const targetTsName = value.targetTableName;
|
|
939
|
-
const
|
|
1101
|
+
const relationSchema = tableHelper({
|
|
940
1102
|
db,
|
|
941
1103
|
table: targetTsName
|
|
942
|
-
})
|
|
1104
|
+
});
|
|
1105
|
+
const referenceModel = whereArgImplementer({ dbName: relationSchema.dbName });
|
|
943
1106
|
acc[key] = t.field({
|
|
944
1107
|
type: referenceModel,
|
|
945
1108
|
required: false
|
|
@@ -1145,6 +1308,11 @@ function buildPothosResponseTypeFromGraphQLType({ builder, sqlType, fieldName, n
|
|
|
1145
1308
|
});
|
|
1146
1309
|
switch (gqlType) {
|
|
1147
1310
|
case "Int": return isArray ? builder.exposeIntList(fieldName, { nullable }) : builder.exposeInt(fieldName, { nullable });
|
|
1311
|
+
case "BigInt": return builder.field({
|
|
1312
|
+
type: isArray ? ["BigInt"] : "BigInt",
|
|
1313
|
+
resolve: (element) => element[fieldName],
|
|
1314
|
+
nullable
|
|
1315
|
+
});
|
|
1148
1316
|
case "String": return isArray ? builder.exposeStringList(fieldName, { nullable }) : builder.exposeString(fieldName, { nullable });
|
|
1149
1317
|
case "Boolean": return isArray ? builder.exposeBooleanList(fieldName, { nullable }) : builder.exposeBoolean(fieldName, { nullable });
|
|
1150
1318
|
case "Date": return builder.field({
|
|
@@ -1154,7 +1322,11 @@ function buildPothosResponseTypeFromGraphQLType({ builder, sqlType, fieldName, n
|
|
|
1154
1322
|
});
|
|
1155
1323
|
case "DateTime": return builder.field({
|
|
1156
1324
|
type: isArray ? ["DateTime"] : "DateTime",
|
|
1157
|
-
resolve: (element) =>
|
|
1325
|
+
resolve: (element) => {
|
|
1326
|
+
const v = element[fieldName];
|
|
1327
|
+
if (v == null) return v;
|
|
1328
|
+
return v instanceof Date ? v : new Date(v);
|
|
1329
|
+
},
|
|
1158
1330
|
nullable
|
|
1159
1331
|
});
|
|
1160
1332
|
case "Float": return isArray ? builder.exposeFloatList(fieldName, { nullable }) : builder.exposeFloat(fieldName, { nullable });
|
|
@@ -1164,6 +1336,15 @@ function buildPothosResponseTypeFromGraphQLType({ builder, sqlType, fieldName, n
|
|
|
1164
1336
|
resolve: (element) => element[fieldName],
|
|
1165
1337
|
nullable
|
|
1166
1338
|
});
|
|
1339
|
+
case "Bytes": return builder.field({
|
|
1340
|
+
type: "Bytes",
|
|
1341
|
+
resolve: (element) => {
|
|
1342
|
+
const v = element[fieldName];
|
|
1343
|
+
if (v == null) return v;
|
|
1344
|
+
return Buffer.isBuffer(v) ? v.toString("base64") : String(v);
|
|
1345
|
+
},
|
|
1346
|
+
nullable
|
|
1347
|
+
});
|
|
1167
1348
|
default: throw new RumbleError(`Unsupported object type ${gqlType} for column ${fieldName}`);
|
|
1168
1349
|
}
|
|
1169
1350
|
}
|
|
@@ -1195,14 +1376,17 @@ function isPostgresDB(db) {
|
|
|
1195
1376
|
//#endregion
|
|
1196
1377
|
//#region lib/search.ts
|
|
1197
1378
|
async function initSearchIfApplicable(input) {
|
|
1379
|
+
const log = input.logger?.enabled ? input.logger.logger : void 0;
|
|
1198
1380
|
if (!isPostgresDB(input.db)) {
|
|
1199
|
-
|
|
1381
|
+
const msg = "Database dialect is not compatible with search, skipping search initialization. Only PostgreSQL is supported.";
|
|
1382
|
+
log ? log.info({}, msg) : console.info(msg);
|
|
1200
1383
|
return;
|
|
1201
1384
|
}
|
|
1202
1385
|
try {
|
|
1203
1386
|
await input.db.execute(drizzle_orm.sql`CREATE EXTENSION IF NOT EXISTS pg_trgm;`);
|
|
1204
1387
|
} catch (error) {
|
|
1205
|
-
|
|
1388
|
+
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.";
|
|
1389
|
+
log ? log.error({ err: error }, msg) : console.error(msg, error);
|
|
1206
1390
|
return;
|
|
1207
1391
|
}
|
|
1208
1392
|
if (input.search?.threshold) {
|
|
@@ -1261,13 +1445,17 @@ const isProbablyAConfigObject = (t) => {
|
|
|
1261
1445
|
].find((e) => e === k))) return true;
|
|
1262
1446
|
return false;
|
|
1263
1447
|
};
|
|
1264
|
-
const createObjectImplementer = ({ db, search, schemaBuilder, makePubSubInstance, whereArgImplementer, orderArgImplementer, enumImplementer, abilityBuilder }) => {
|
|
1448
|
+
const createObjectImplementer = ({ db, search, logger: loggerConfig, schemaBuilder, makePubSubInstance, whereArgImplementer, orderArgImplementer, enumImplementer, abilityBuilder }) => {
|
|
1265
1449
|
return ({ table, refName, readAction = "read", adjust }) => {
|
|
1266
1450
|
const tableSchema = tableHelper({
|
|
1267
1451
|
db,
|
|
1268
1452
|
table
|
|
1269
1453
|
});
|
|
1270
|
-
if (Object.keys(tableSchema.primaryKey).length === 0)
|
|
1454
|
+
if (Object.keys(tableSchema.primaryKey).length === 0) {
|
|
1455
|
+
const log = loggerConfig?.enabled ? loggerConfig.logger : void 0;
|
|
1456
|
+
const msg = `Could not find primary key for ${table.toString()}. Cannot register subscriptions!`;
|
|
1457
|
+
log ? log.warn({ "rumble.table": table.toString() }, msg) : console.warn(msg);
|
|
1458
|
+
}
|
|
1271
1459
|
const primaryKey = Object.values(tableSchema.primaryKey)[0];
|
|
1272
1460
|
const { registerOnInstance } = makePubSubInstance({ table });
|
|
1273
1461
|
return schemaBuilder.drizzleObject(table, {
|
|
@@ -1276,7 +1464,9 @@ const createObjectImplementer = ({ db, search, schemaBuilder, makePubSubInstance
|
|
|
1276
1464
|
if (!primaryKey) return;
|
|
1277
1465
|
const primaryKeyValue = element[primaryKey.name];
|
|
1278
1466
|
if (!primaryKeyValue) {
|
|
1279
|
-
|
|
1467
|
+
const log = loggerConfig?.enabled ? loggerConfig.logger : void 0;
|
|
1468
|
+
const msg = `Could not find primary key value for element on ${table.toString()}. Cannot register subscription!`;
|
|
1469
|
+
log ? log.warn({ "rumble.table": table.toString() }, msg) : console.warn(msg);
|
|
1280
1470
|
return;
|
|
1281
1471
|
}
|
|
1282
1472
|
registerOnInstance({
|
|
@@ -1307,6 +1497,10 @@ const createObjectImplementer = ({ db, search, schemaBuilder, makePubSubInstance
|
|
|
1307
1497
|
};
|
|
1308
1498
|
} })) ?? {};
|
|
1309
1499
|
const fields = Object.entries(columns).reduce((acc, [key, column]) => {
|
|
1500
|
+
if (key.startsWith("__")) {
|
|
1501
|
+
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.`);
|
|
1502
|
+
return acc;
|
|
1503
|
+
}
|
|
1310
1504
|
if (userAdjustments[key]) {
|
|
1311
1505
|
const { params, creatorFunction, configObject } = configMap.get(userAdjustments[key]);
|
|
1312
1506
|
if (typeof configObject.nullable !== "boolean") configObject.nullable = !column.notNull;
|
|
@@ -1507,7 +1701,8 @@ const createPubSubInstance = ({ subscriptions }) => {
|
|
|
1507
1701
|
};
|
|
1508
1702
|
//#endregion
|
|
1509
1703
|
//#region lib/query.ts
|
|
1510
|
-
const createQueryImplementer = ({ db, schemaBuilder, search, whereArgImplementer, orderArgImplementer, makePubSubInstance }) => {
|
|
1704
|
+
const createQueryImplementer = ({ db, schemaBuilder, search, logger: loggerConfig, whereArgImplementer, orderArgImplementer, makePubSubInstance }) => {
|
|
1705
|
+
const registeredQueryFieldNames = /* @__PURE__ */ new Set();
|
|
1511
1706
|
return ({ table, readAction = "read", listAction = "read" }) => {
|
|
1512
1707
|
const WhereArg = whereArgImplementer({ table });
|
|
1513
1708
|
const OrderArg = orderArgImplementer({ table });
|
|
@@ -1517,6 +1712,12 @@ const createQueryImplementer = ({ db, schemaBuilder, search, whereArgImplementer
|
|
|
1517
1712
|
});
|
|
1518
1713
|
const primaryKeyField = Object.values(tableSchema.primaryKey)[0];
|
|
1519
1714
|
const { registerOnInstance } = makePubSubInstance({ table });
|
|
1715
|
+
const pluralName = pluralize.default.plural(table.toString());
|
|
1716
|
+
const singularName = pluralize.default.singular(table.toString());
|
|
1717
|
+
for (const name of [pluralName, singularName]) {
|
|
1718
|
+
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").`);
|
|
1719
|
+
registeredQueryFieldNames.add(name);
|
|
1720
|
+
}
|
|
1520
1721
|
return schemaBuilder.queryFields((t) => {
|
|
1521
1722
|
const manyArgs = {
|
|
1522
1723
|
where: t.arg({
|
|
@@ -1533,11 +1734,11 @@ const createQueryImplementer = ({ db, schemaBuilder, search, whereArgImplementer
|
|
|
1533
1734
|
};
|
|
1534
1735
|
if (!search?.enabled) delete manyArgs.search;
|
|
1535
1736
|
return {
|
|
1536
|
-
[
|
|
1737
|
+
[pluralName]: t.drizzleField({
|
|
1537
1738
|
type: [table],
|
|
1538
1739
|
nullable: false,
|
|
1539
1740
|
smartSubscription: true,
|
|
1540
|
-
description: `List all ${
|
|
1741
|
+
description: `List all ${pluralName}`,
|
|
1541
1742
|
subscribe: (subscriptions, _root, _args, _ctx, _info) => {
|
|
1542
1743
|
registerOnInstance({
|
|
1543
1744
|
instance: subscriptions,
|
|
@@ -1565,17 +1766,21 @@ const createQueryImplementer = ({ db, schemaBuilder, search, whereArgImplementer
|
|
|
1565
1766
|
if (filter.columns) queryInstance.columns = filter.columns;
|
|
1566
1767
|
if (search?.enabled && args.search && args.search.length > 0 && search?.cpu_operator_cost) return db.transaction(async (tx) => {
|
|
1567
1768
|
if (isPostgresDB(tx)) await tx.execute(drizzle_orm.sql.raw(`SET cpu_operator_cost = ${search.cpu_operator_cost};`));
|
|
1568
|
-
else
|
|
1769
|
+
else {
|
|
1770
|
+
const msg = "Database dialect is not postgresql, cannot set cpu_operator_cost.";
|
|
1771
|
+
const log = loggerConfig?.enabled ? loggerConfig.logger : void 0;
|
|
1772
|
+
log ? log.info({}, msg) : console.info(msg);
|
|
1773
|
+
}
|
|
1569
1774
|
return tx.query[table].findMany(queryInstance);
|
|
1570
1775
|
});
|
|
1571
1776
|
return db.query[table].findMany(queryInstance);
|
|
1572
1777
|
}
|
|
1573
1778
|
}),
|
|
1574
|
-
[
|
|
1779
|
+
[singularName]: t.drizzleField({
|
|
1575
1780
|
type: table,
|
|
1576
1781
|
nullable: false,
|
|
1577
1782
|
smartSubscription: true,
|
|
1578
|
-
description: `Get a single ${
|
|
1783
|
+
description: `Get a single ${singularName} by ID`,
|
|
1579
1784
|
args: { id: t.arg.id({ required: true }) },
|
|
1580
1785
|
resolve: (query, _root, args, ctx, _info) => {
|
|
1581
1786
|
deepSetProto(args);
|
|
@@ -1751,6 +1956,96 @@ function implementDefaultWhereInputArgs(schemaBuilder) {
|
|
|
1751
1956
|
OR: t.field({ type: [JSONWhereInputArgument] }),
|
|
1752
1957
|
NOT: t.field({ type: JSONWhereInputArgument })
|
|
1753
1958
|
}) });
|
|
1959
|
+
const BigIntWhereInputArgument = schemaBuilder.inputRef("BigIntWhereInputArgument").implement({ fields: (t) => ({
|
|
1960
|
+
eq: t.field({
|
|
1961
|
+
type: "BigInt",
|
|
1962
|
+
required: false
|
|
1963
|
+
}),
|
|
1964
|
+
ne: t.field({
|
|
1965
|
+
type: "BigInt",
|
|
1966
|
+
required: false
|
|
1967
|
+
}),
|
|
1968
|
+
gt: t.field({
|
|
1969
|
+
type: "BigInt",
|
|
1970
|
+
required: false
|
|
1971
|
+
}),
|
|
1972
|
+
gte: t.field({
|
|
1973
|
+
type: "BigInt",
|
|
1974
|
+
required: false
|
|
1975
|
+
}),
|
|
1976
|
+
lt: t.field({
|
|
1977
|
+
type: "BigInt",
|
|
1978
|
+
required: false
|
|
1979
|
+
}),
|
|
1980
|
+
lte: t.field({
|
|
1981
|
+
type: "BigInt",
|
|
1982
|
+
required: false
|
|
1983
|
+
}),
|
|
1984
|
+
in: t.field({
|
|
1985
|
+
type: ["BigInt"],
|
|
1986
|
+
required: false
|
|
1987
|
+
}),
|
|
1988
|
+
notIn: t.field({
|
|
1989
|
+
type: ["BigInt"],
|
|
1990
|
+
required: false
|
|
1991
|
+
}),
|
|
1992
|
+
isNull: t.boolean(),
|
|
1993
|
+
isNotNull: t.boolean(),
|
|
1994
|
+
AND: t.field({ type: [BigIntWhereInputArgument] }),
|
|
1995
|
+
OR: t.field({ type: [BigIntWhereInputArgument] }),
|
|
1996
|
+
NOT: t.field({ type: BigIntWhereInputArgument })
|
|
1997
|
+
}) });
|
|
1998
|
+
const DateTimeWhereInputArgument = schemaBuilder.inputRef("DateTimeWhereInputArgument").implement({ fields: (t) => ({
|
|
1999
|
+
eq: t.field({
|
|
2000
|
+
type: "DateTime",
|
|
2001
|
+
required: false
|
|
2002
|
+
}),
|
|
2003
|
+
ne: t.field({
|
|
2004
|
+
type: "DateTime",
|
|
2005
|
+
required: false
|
|
2006
|
+
}),
|
|
2007
|
+
gt: t.field({
|
|
2008
|
+
type: "DateTime",
|
|
2009
|
+
required: false
|
|
2010
|
+
}),
|
|
2011
|
+
gte: t.field({
|
|
2012
|
+
type: "DateTime",
|
|
2013
|
+
required: false
|
|
2014
|
+
}),
|
|
2015
|
+
lt: t.field({
|
|
2016
|
+
type: "DateTime",
|
|
2017
|
+
required: false
|
|
2018
|
+
}),
|
|
2019
|
+
lte: t.field({
|
|
2020
|
+
type: "DateTime",
|
|
2021
|
+
required: false
|
|
2022
|
+
}),
|
|
2023
|
+
in: t.field({
|
|
2024
|
+
type: ["DateTime"],
|
|
2025
|
+
required: false
|
|
2026
|
+
}),
|
|
2027
|
+
notIn: t.field({
|
|
2028
|
+
type: ["DateTime"],
|
|
2029
|
+
required: false
|
|
2030
|
+
}),
|
|
2031
|
+
isNull: t.boolean(),
|
|
2032
|
+
isNotNull: t.boolean(),
|
|
2033
|
+
arrayOverlaps: t.field({
|
|
2034
|
+
type: ["DateTime"],
|
|
2035
|
+
required: false
|
|
2036
|
+
}),
|
|
2037
|
+
arrayContained: t.field({
|
|
2038
|
+
type: ["DateTime"],
|
|
2039
|
+
required: false
|
|
2040
|
+
}),
|
|
2041
|
+
arrayContains: t.field({
|
|
2042
|
+
type: ["DateTime"],
|
|
2043
|
+
required: false
|
|
2044
|
+
}),
|
|
2045
|
+
AND: t.field({ type: [DateTimeWhereInputArgument] }),
|
|
2046
|
+
OR: t.field({ type: [DateTimeWhereInputArgument] }),
|
|
2047
|
+
NOT: t.field({ type: DateTimeWhereInputArgument })
|
|
2048
|
+
}) });
|
|
1754
2049
|
}
|
|
1755
2050
|
//#endregion
|
|
1756
2051
|
//#region lib/runtimeFiltersPlugin/filterTypes.ts
|
|
@@ -1761,9 +2056,11 @@ const applyFiltersKey = "applyFilters";
|
|
|
1761
2056
|
var RuntimeFiltersPlugin = class extends _pothos_core.BasePlugin {
|
|
1762
2057
|
tracer;
|
|
1763
2058
|
tracerEnabled;
|
|
2059
|
+
logger;
|
|
1764
2060
|
onTypeConfig(typeConfig) {
|
|
1765
2061
|
this.tracer = this.builder.options.otel?.tracer;
|
|
1766
2062
|
this.tracerEnabled = this.builder.options.otel?.enabled;
|
|
2063
|
+
this.logger = this.builder.options.logger?.enabled ? this.builder.options.logger.logger : void 0;
|
|
1767
2064
|
return typeConfig;
|
|
1768
2065
|
}
|
|
1769
2066
|
wrapResolve(resolver, fieldConfig) {
|
|
@@ -1792,7 +2089,8 @@ var RuntimeFiltersPlugin = class extends _pothos_core.BasePlugin {
|
|
|
1792
2089
|
}));
|
|
1793
2090
|
let resolved, prefetchedFilters;
|
|
1794
2091
|
if (this.tracer && this.tracerEnabled) {
|
|
1795
|
-
const o = await this.tracer.startActiveSpan(`
|
|
2092
|
+
const o = await this.tracer.startActiveSpan(`rumble.filter.resolve`, async (span) => {
|
|
2093
|
+
span.setAttribute("graphql.field.name", fieldConfig.name);
|
|
1796
2094
|
try {
|
|
1797
2095
|
return await Promise.all([resolver(parent, args, context, info), prefetchedFiltersPromises]);
|
|
1798
2096
|
} finally {
|
|
@@ -1814,17 +2112,37 @@ var RuntimeFiltersPlugin = class extends _pothos_core.BasePlugin {
|
|
|
1814
2112
|
return acc;
|
|
1815
2113
|
}, /* @__PURE__ */ new Set()));
|
|
1816
2114
|
span?.setAttribute("filters.allowed", allowed.length);
|
|
2115
|
+
this.logger?.debug({
|
|
2116
|
+
"graphql.field.name": fieldConfig.name,
|
|
2117
|
+
"filters.total": allFilters.length,
|
|
2118
|
+
"filters.allowed": allowed.length
|
|
2119
|
+
}, "runtime filters applied");
|
|
1817
2120
|
if (Array.isArray(resolved)) return allowed;
|
|
1818
2121
|
return allowed[0] ?? null;
|
|
1819
2122
|
};
|
|
1820
|
-
if (this.tracer && this.tracerEnabled) return this.tracer.startActiveSpan(`
|
|
2123
|
+
if (this.tracer && this.tracerEnabled) return this.tracer.startActiveSpan(`rumble.filter.apply`, async (span) => {
|
|
2124
|
+
span.setAttribute("graphql.field.name", fieldConfig.name);
|
|
1821
2125
|
try {
|
|
1822
2126
|
return await runFilters(span);
|
|
2127
|
+
} catch (error) {
|
|
2128
|
+
this.logger?.error({
|
|
2129
|
+
"graphql.field.name": fieldConfig.name,
|
|
2130
|
+
err: error
|
|
2131
|
+
}, "runtime filter threw");
|
|
2132
|
+
throw error;
|
|
1823
2133
|
} finally {
|
|
1824
2134
|
span.end();
|
|
1825
2135
|
}
|
|
1826
2136
|
});
|
|
1827
|
-
else
|
|
2137
|
+
else try {
|
|
2138
|
+
return await runFilters();
|
|
2139
|
+
} catch (error) {
|
|
2140
|
+
this.logger?.error({
|
|
2141
|
+
"graphql.field.name": fieldConfig.name,
|
|
2142
|
+
err: error
|
|
2143
|
+
}, "runtime filter threw");
|
|
2144
|
+
throw error;
|
|
2145
|
+
}
|
|
1828
2146
|
};
|
|
1829
2147
|
}
|
|
1830
2148
|
};
|
|
@@ -1837,7 +2155,7 @@ function registerRuntimeFiltersPlugin() {
|
|
|
1837
2155
|
}
|
|
1838
2156
|
//#endregion
|
|
1839
2157
|
//#region lib/schemaBuilder.ts
|
|
1840
|
-
const createSchemaBuilder = ({ db,
|
|
2158
|
+
const createSchemaBuilder = ({ db, pubsub, pothosConfig, otel, logger }) => {
|
|
1841
2159
|
const createSpan = otel?.enabled && otel.tracer ? (0, _pothos_tracing_opentelemetry.createOpenTelemetryWrapper)(otel.tracer, otel.options) : void 0;
|
|
1842
2160
|
registerRuntimeFiltersPlugin();
|
|
1843
2161
|
const schemaBuilder = new _pothos_core.default({
|
|
@@ -1865,18 +2183,40 @@ const createSchemaBuilder = ({ db, disableDefaultObjects, pubsub, pothosConfig,
|
|
|
1865
2183
|
}) },
|
|
1866
2184
|
defaultFieldNullability: false,
|
|
1867
2185
|
tracing: {
|
|
1868
|
-
default: otel?.enabled ? (config) => (0, _pothos_plugin_tracing.isRootField)(config) : () => false,
|
|
1869
|
-
wrap:
|
|
2186
|
+
default: otel?.enabled || logger?.enabled ? (config) => (0, _pothos_plugin_tracing.isRootField)(config) : () => false,
|
|
2187
|
+
wrap: (resolver, options, config) => {
|
|
2188
|
+
let r = createSpan ? createSpan(resolver, options) : resolver;
|
|
2189
|
+
if (logger?.enabled) {
|
|
2190
|
+
const log = logger.logger;
|
|
2191
|
+
r = (0, _pothos_plugin_tracing.wrapResolver)(r, (error, duration) => {
|
|
2192
|
+
if (error) log.error({
|
|
2193
|
+
"graphql.field.name": config.name,
|
|
2194
|
+
"graphql.parent.type": config.parentType,
|
|
2195
|
+
durationMs: duration,
|
|
2196
|
+
err: error
|
|
2197
|
+
}, "resolver failed");
|
|
2198
|
+
else log.debug({
|
|
2199
|
+
"graphql.field.name": config.name,
|
|
2200
|
+
"graphql.parent.type": config.parentType,
|
|
2201
|
+
durationMs: duration
|
|
2202
|
+
}, "resolver completed");
|
|
2203
|
+
});
|
|
2204
|
+
}
|
|
2205
|
+
return r;
|
|
2206
|
+
}
|
|
1870
2207
|
},
|
|
1871
|
-
otel
|
|
2208
|
+
otel,
|
|
2209
|
+
logger
|
|
1872
2210
|
});
|
|
1873
2211
|
schemaBuilder.addScalarType("JSON", graphql_scalars.JSONResolver);
|
|
1874
2212
|
schemaBuilder.addScalarType("Date", graphql_scalars.DateResolver);
|
|
1875
2213
|
schemaBuilder.addScalarType("DateTime", graphql_scalars.DateTimeISOResolver);
|
|
2214
|
+
schemaBuilder.addScalarType("BigInt", graphql_scalars.BigIntResolver);
|
|
2215
|
+
schemaBuilder.addScalarType("Bytes", graphql_scalars.ByteResolver);
|
|
1876
2216
|
implementDefaultWhereInputArgs(schemaBuilder);
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
2217
|
+
schemaBuilder.queryType({});
|
|
2218
|
+
schemaBuilder.subscriptionType({});
|
|
2219
|
+
schemaBuilder.mutationType({});
|
|
1880
2220
|
return { schemaBuilder };
|
|
1881
2221
|
};
|
|
1882
2222
|
//#endregion
|
|
@@ -1959,7 +2299,33 @@ export const r = rumble({
|
|
|
1959
2299
|
whereArgImplementer: whereArg,
|
|
1960
2300
|
makePubSubInstance
|
|
1961
2301
|
});
|
|
1962
|
-
const builtSchema = require_lazy.lazy(() =>
|
|
2302
|
+
const builtSchema = require_lazy.lazy(() => {
|
|
2303
|
+
let raw;
|
|
2304
|
+
try {
|
|
2305
|
+
raw = schemaBuilder.toSchema();
|
|
2306
|
+
} catch (e) {
|
|
2307
|
+
const unimplMatch = (e?.message ?? String(e)).match(/ObjectRef<(.+?)> has not been implemented/);
|
|
2308
|
+
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.`);
|
|
2309
|
+
throw e;
|
|
2310
|
+
}
|
|
2311
|
+
const mutation = raw.getMutationType();
|
|
2312
|
+
const subscription = raw.getSubscriptionType();
|
|
2313
|
+
const emptyMutation = mutation && Object.keys(mutation.getFields()).length === 0;
|
|
2314
|
+
const emptySubscription = subscription && Object.keys(subscription.getFields()).length === 0;
|
|
2315
|
+
if (!emptyMutation && !emptySubscription) return raw;
|
|
2316
|
+
const config = raw.toConfig();
|
|
2317
|
+
const filteredTypes = config.types.filter((t) => {
|
|
2318
|
+
if (emptyMutation && t === mutation) return false;
|
|
2319
|
+
if (emptySubscription && t === subscription) return false;
|
|
2320
|
+
return true;
|
|
2321
|
+
});
|
|
2322
|
+
return new graphql.GraphQLSchema({
|
|
2323
|
+
...config,
|
|
2324
|
+
mutation: emptyMutation ? void 0 : config.mutation,
|
|
2325
|
+
subscription: emptySubscription ? void 0 : config.subscription,
|
|
2326
|
+
types: filteredTypes
|
|
2327
|
+
});
|
|
2328
|
+
});
|
|
1963
2329
|
const createYoga = (args) => {
|
|
1964
2330
|
const enableApiDocs = args?.enableApiDocs ?? process?.env?.NODE_ENV === "development";
|
|
1965
2331
|
return (0, graphql_yoga.createYoga)({
|
|
@@ -1970,26 +2336,60 @@ export const r = rumble({
|
|
|
1970
2336
|
plugins: [
|
|
1971
2337
|
...args?.plugins ?? [],
|
|
1972
2338
|
...enableApiDocs ? [] : [(0, _graphql_yoga_plugin_disable_introspection.useDisableIntrospection)(), (0, _escape_tech_graphql_armor.EnvelopArmorPlugin)(args?.armorConfig)],
|
|
1973
|
-
rumbleInput.otel?.enabled ? { onExecute: ({ setExecuteFn, executeFn }) => {
|
|
1974
|
-
setExecuteFn((options) =>
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
2339
|
+
rumbleInput.otel?.enabled || rumbleInput.logger?.enabled ? { onExecute: ({ setExecuteFn, executeFn }) => {
|
|
2340
|
+
setExecuteFn(async (options) => {
|
|
2341
|
+
const log = rumbleInput.logger?.enabled ? rumbleInput.logger.logger : void 0;
|
|
2342
|
+
const operationName = options.operationName ?? "anonymous";
|
|
2343
|
+
const start = Date.now();
|
|
2344
|
+
log?.info({ "graphql.operation.name": operationName }, "graphql execute start");
|
|
2345
|
+
const run = async () => {
|
|
1979
2346
|
const result = await executeFn(options);
|
|
1980
|
-
if (result && "errors" in result && result.errors?.length) {
|
|
1981
|
-
|
|
2347
|
+
if (result && "errors" in result && result.errors?.length) log?.error({
|
|
2348
|
+
"graphql.operation.name": operationName,
|
|
2349
|
+
durationMs: Date.now() - start,
|
|
2350
|
+
errors: result.errors.map((e) => e.message)
|
|
2351
|
+
}, "graphql execute completed with errors");
|
|
2352
|
+
else log?.info({
|
|
2353
|
+
"graphql.operation.name": operationName,
|
|
2354
|
+
durationMs: Date.now() - start
|
|
2355
|
+
}, "graphql execute completed");
|
|
2356
|
+
return result;
|
|
2357
|
+
};
|
|
2358
|
+
if (rumbleInput.otel?.enabled) return rumbleInput.otel.tracer.startActiveSpan(_pothos_tracing_opentelemetry.SpanNames.EXECUTE, { attributes: {
|
|
2359
|
+
[_pothos_tracing_opentelemetry.AttributeNames.OPERATION_NAME]: operationName,
|
|
2360
|
+
[_pothos_tracing_opentelemetry.AttributeNames.SOURCE]: options.document
|
|
2361
|
+
} }, async (span) => {
|
|
2362
|
+
try {
|
|
2363
|
+
const result = await run();
|
|
2364
|
+
if (result && "errors" in result && result.errors?.length) {
|
|
2365
|
+
for (const error of result.errors) span.recordException(error);
|
|
2366
|
+
span.setStatus({ code: _opentelemetry_api.SpanStatusCode.ERROR });
|
|
2367
|
+
}
|
|
2368
|
+
return result;
|
|
2369
|
+
} catch (error) {
|
|
2370
|
+
if (error instanceof Error) span.recordException(error);
|
|
2371
|
+
log?.error({
|
|
2372
|
+
"graphql.operation.name": operationName,
|
|
2373
|
+
durationMs: Date.now() - start,
|
|
2374
|
+
err: error
|
|
2375
|
+
}, "graphql execute threw");
|
|
1982
2376
|
span.setStatus({ code: _opentelemetry_api.SpanStatusCode.ERROR });
|
|
2377
|
+
throw error;
|
|
2378
|
+
} finally {
|
|
2379
|
+
span.end();
|
|
1983
2380
|
}
|
|
1984
|
-
|
|
2381
|
+
});
|
|
2382
|
+
try {
|
|
2383
|
+
return await run();
|
|
1985
2384
|
} catch (error) {
|
|
1986
|
-
|
|
1987
|
-
|
|
2385
|
+
log?.error({
|
|
2386
|
+
"graphql.operation.name": operationName,
|
|
2387
|
+
durationMs: Date.now() - start,
|
|
2388
|
+
err: error
|
|
2389
|
+
}, "graphql execute threw");
|
|
1988
2390
|
throw error;
|
|
1989
|
-
} finally {
|
|
1990
|
-
span.end();
|
|
1991
2391
|
}
|
|
1992
|
-
})
|
|
2392
|
+
});
|
|
1993
2393
|
} } : false
|
|
1994
2394
|
].filter(Boolean)
|
|
1995
2395
|
});
|