@vantreeseba/drizzle-graphql 4.1.0 → 5.0.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/dist/index.cjs CHANGED
@@ -33,6 +33,7 @@ __export(index_exports, {
33
33
  GraphQLBigIntString: () => GraphQLBigIntString,
34
34
  GraphQLDate: () => import_graphql_scalars.GraphQLDate,
35
35
  GraphQLDateTime: () => import_graphql_scalars.GraphQLDateTime,
36
+ GraphQLDecimalString: () => GraphQLDecimalString,
36
37
  GraphQLJSON: () => import_graphql_scalars.GraphQLJSON,
37
38
  GraphQLUUID: () => import_graphql_scalars.GraphQLUUID,
38
39
  buildSchema: () => buildSchema,
@@ -298,13 +299,6 @@ var remapFromGraphQLArrayInput = (queryInput, table) => {
298
299
  return queryInput;
299
300
  };
300
301
 
301
- // src/util/type-converter/index.ts
302
- var import_drizzle_orm2 = require("drizzle-orm");
303
- var import_mysql_core = require("drizzle-orm/mysql-core");
304
- var import_pg_core = require("drizzle-orm/pg-core");
305
- var import_sqlite_core = require("drizzle-orm/sqlite-core");
306
- var import_graphql3 = require("graphql");
307
-
308
302
  // src/util/scalars/index.ts
309
303
  var import_graphql2 = require("graphql");
310
304
  var import_graphql_scalars = require("graphql-scalars");
@@ -343,8 +337,44 @@ var GraphQLBigIntString = new import_graphql2.GraphQLScalarType({
343
337
  return asDecimalString(ast.value);
344
338
  }
345
339
  });
340
+ var numericStringPattern = /^[+-]?(\d+(\.\d*)?|\.\d+)([eE][+-]?\d+)?$/;
341
+ var asNumericString = (value) => {
342
+ if (typeof value === "bigint") {
343
+ return value.toString();
344
+ }
345
+ if (typeof value === "number") {
346
+ if (!Number.isFinite(value)) {
347
+ throw new import_graphql2.GraphQLError(`Decimal cannot represent non-finite value: ${value}`);
348
+ }
349
+ return String(value);
350
+ }
351
+ if (typeof value === "string") {
352
+ if (!numericStringPattern.test(value)) {
353
+ throw new import_graphql2.GraphQLError(`Decimal cannot represent non-numeric value: "${value}"`);
354
+ }
355
+ return value;
356
+ }
357
+ throw new import_graphql2.GraphQLError(`Decimal cannot represent value: ${JSON.stringify(value)}`);
358
+ };
359
+ var GraphQLDecimalString = new import_graphql2.GraphQLScalarType({
360
+ name: "Decimal",
361
+ description: "An arbitrary-precision decimal, transported as a numeric string so that values beyond JavaScript's double-precision range survive the round-trip intact.",
362
+ serialize: asNumericString,
363
+ parseValue: asNumericString,
364
+ parseLiteral: (ast) => {
365
+ if (ast.kind !== import_graphql2.Kind.STRING && ast.kind !== import_graphql2.Kind.INT && ast.kind !== import_graphql2.Kind.FLOAT) {
366
+ throw new import_graphql2.GraphQLError(`Decimal cannot represent a ${ast.kind}`, { nodes: ast });
367
+ }
368
+ return asNumericString(ast.value);
369
+ }
370
+ });
346
371
 
347
372
  // src/util/type-converter/index.ts
373
+ var import_drizzle_orm2 = require("drizzle-orm");
374
+ var import_mysql_core = require("drizzle-orm/mysql-core");
375
+ var import_pg_core = require("drizzle-orm/pg-core");
376
+ var import_sqlite_core = require("drizzle-orm/sqlite-core");
377
+ var import_graphql3 = require("graphql");
348
378
  var allowedNameChars = /^[a-zA-Z0-9_]+$/;
349
379
  var enumMap = /* @__PURE__ */ new WeakMap();
350
380
  var generateEnumCached = (column, columnName, tableName) => {
@@ -381,7 +411,7 @@ var geoXyInputType = new import_graphql3.GraphQLInputObjectType({
381
411
  }
382
412
  });
383
413
  var columnToGraphQLCore = (column, columnName, tableName, isInput) => {
384
- const { type: baseType } = (0, import_drizzle_orm2.extractExtendedColumnType)(column);
414
+ const { type: baseType, constraint } = (0, import_drizzle_orm2.extractExtendedColumnType)(column);
385
415
  switch (baseType) {
386
416
  case "boolean":
387
417
  return { type: import_graphql3.GraphQLBoolean, description: "Boolean" };
@@ -400,6 +430,9 @@ var columnToGraphQLCore = (column, columnName, tableName, isInput) => {
400
430
  if (column.enumValues?.length) {
401
431
  return { type: generateEnumCached(column, columnName, tableName) };
402
432
  }
433
+ if (constraint === "numeric" && !column.dimensions) {
434
+ return { type: GraphQLDecimalString, description: "Decimal" };
435
+ }
403
436
  if (column instanceof import_pg_core.PgTimestamp || column instanceof import_pg_core.PgTimestampString) {
404
437
  return { type: import_graphql_scalars.GraphQLDateTime, description: "DateTime" };
405
438
  }
@@ -409,6 +442,15 @@ var columnToGraphQLCore = (column, columnName, tableName, isInput) => {
409
442
  if (column instanceof import_pg_core.PgDateString) {
410
443
  return isInput ? { type: import_graphql3.GraphQLString, description: "Date" } : { type: import_graphql_scalars.GraphQLDate, description: "Date" };
411
444
  }
445
+ {
446
+ const dims = column.dimensions;
447
+ if (dims !== void 0 && dims > 0) {
448
+ return {
449
+ type: new import_graphql3.GraphQLList(new import_graphql3.GraphQLNonNull(import_graphql3.GraphQLString)),
450
+ description: "Array<String>"
451
+ };
452
+ }
453
+ }
412
454
  return { type: import_graphql3.GraphQLString, description: "String" };
413
455
  case "bigint":
414
456
  return { type: GraphQLBigIntString, description: "BigInt" };
@@ -559,10 +601,10 @@ var resolveQueryExecutor = (db, context, tableName, buildTimeQueryBase) => {
559
601
  queryBase: buildTimeQueryBase ? executor?.query?.[tableName] ?? buildTimeQueryBase : buildTimeQueryBase
560
602
  };
561
603
  };
562
- var batchedPaginatedRelationQuery = async (db, targetTable, foreignCol, whereCondition, orderByArg, limit, offset, pkNames) => {
604
+ var batchedPaginatedRelationQuery = async (db, targetTable, foreignCol, whereCondition, orderByArg, limit, offset, pkNames, orderCtx) => {
563
605
  const cols = (0, import_drizzle_orm3.getColumns)(targetTable);
564
606
  const orderExprs = [
565
- ...orderByArg ? extractOrderBy(targetTable, orderByArg) : [],
607
+ ...orderByArg ? extractOrderBy(targetTable, orderByArg, orderCtx) : [],
566
608
  ...primaryKeyOrderExprs(targetTable, pkNames)
567
609
  ];
568
610
  const orderClause = orderExprs.length ? import_drizzle_orm3.sql` order by ${import_drizzle_orm3.sql.join(orderExprs, import_drizzle_orm3.sql`, `)}` : import_drizzle_orm3.sql``;
@@ -626,12 +668,13 @@ var createRelationResolverFactory = (db, tables, filterCtx) => ({ tableName, rel
626
668
  orderByArg,
627
669
  limit ?? null,
628
670
  offset ?? null,
629
- targetPkNames
671
+ targetPkNames,
672
+ relationFilterCtx(filterCtx, targetTableName)
630
673
  );
631
674
  } else {
632
675
  let q = executor.select().from(targetTable).where(whereCondition);
633
676
  if (orderByArg) {
634
- q = q.orderBy(...extractOrderBy(targetTable, orderByArg));
677
+ q = q.orderBy(...extractOrderBy(targetTable, orderByArg, relationFilterCtx(filterCtx, targetTableName)));
635
678
  }
636
679
  rows = await q;
637
680
  }
@@ -720,6 +763,20 @@ var extractSelectedColumnsFromTreeSQLFormat = (tree, table, selectionCtx) => {
720
763
  }
721
764
  return Object.fromEntries(selectedColumns);
722
765
  };
766
+ var orderNulls = new import_graphql4.GraphQLEnumType({
767
+ name: "OrderNulls",
768
+ description: "Where NULL values sort relative to non-NULL values",
769
+ values: {
770
+ first: {
771
+ value: "first",
772
+ description: "NULL values sort before all non-NULL values"
773
+ },
774
+ last: {
775
+ value: "last",
776
+ description: "NULL values sort after all non-NULL values"
777
+ }
778
+ }
779
+ });
723
780
  var innerOrder = new import_graphql4.GraphQLInputObjectType({
724
781
  name: "InnerOrder",
725
782
  fields: {
@@ -744,6 +801,10 @@ var innerOrder = new import_graphql4.GraphQLInputObjectType({
744
801
  priority: {
745
802
  type: new import_graphql4.GraphQLNonNull(import_graphql4.GraphQLInt),
746
803
  description: "Priority of current field"
804
+ },
805
+ nulls: {
806
+ type: orderNulls,
807
+ description: "Where NULL values sort. Defaults to the database's own rule (PostgreSQL: last on asc, first on desc; MySQL/SQLite: first on asc, last on desc)"
747
808
  }
748
809
  }
749
810
  });
@@ -757,9 +818,25 @@ var resolveGenericFilterName = (column, columnName, columnGraphQLType) => {
757
818
  if (columnGraphQLType.type instanceof import_graphql4.GraphQLEnumType) {
758
819
  return columnGraphQLType.type.name;
759
820
  }
821
+ if (columnGraphQLType.type === GraphQLBigIntString) {
822
+ return "BigInt";
823
+ }
824
+ if (columnGraphQLType.type === GraphQLDecimalString) {
825
+ return "Decimal";
826
+ }
760
827
  if (columnGraphQLType.type instanceof import_graphql4.GraphQLList) {
761
828
  const desc2 = columnGraphQLType.description ?? "";
762
- return desc2.includes("Integer") ? "IntArray" : "FloatArray";
829
+ let inner = columnGraphQLType.type.ofType;
830
+ if (inner instanceof import_graphql4.GraphQLNonNull) {
831
+ inner = inner.ofType;
832
+ }
833
+ if (desc2.includes("Integer") || inner === import_graphql4.GraphQLInt) {
834
+ return "IntArray";
835
+ }
836
+ if (desc2.includes("Float") || inner === import_graphql4.GraphQLFloat) {
837
+ return "FloatArray";
838
+ }
839
+ return "StringArray";
763
840
  }
764
841
  const ct = column.columnType ?? "";
765
842
  if (ct === "PgTimestamp" || ct === "PgTimestampString" || ct === "PgDate") {
@@ -772,12 +849,12 @@ var generateColumnFilterValues = (column, tableName, columnName, cacheCtx) => {
772
849
  const genericName = resolveGenericFilterName(column, columnName, columnGraphQLType);
773
850
  const cached = cacheCtx.genericFilterCache.get(genericName);
774
851
  if (cached) {
775
- return cached.main;
852
+ return cached;
776
853
  }
777
854
  const colType = columnGraphQLType.type;
778
855
  const colDesc = columnGraphQLType.description;
779
856
  const colArr = new import_graphql4.GraphQLList(new import_graphql4.GraphQLNonNull(colType));
780
- const isId = genericName === "Id";
857
+ const omitStringOps = genericName === "Id" || genericName === "BigInt" || genericName === "Decimal";
781
858
  const baseFields = {
782
859
  eq: { type: colType, description: colDesc },
783
860
  ne: { type: colType, description: colDesc },
@@ -785,31 +862,60 @@ var generateColumnFilterValues = (column, tableName, columnName, cacheCtx) => {
785
862
  lte: { type: colType, description: colDesc },
786
863
  gt: { type: colType, description: colDesc },
787
864
  gte: { type: colType, description: colDesc },
788
- ...isId ? {} : {
865
+ ...omitStringOps ? {} : {
789
866
  like: { type: import_graphql4.GraphQLString },
790
867
  notLike: { type: import_graphql4.GraphQLString },
791
868
  ilike: { type: import_graphql4.GraphQLString },
792
- notIlike: { type: import_graphql4.GraphQLString }
869
+ notIlike: { type: import_graphql4.GraphQLString },
870
+ startsWith: {
871
+ type: import_graphql4.GraphQLString,
872
+ description: "Matches values starting with the given string. `%`, `_` and `\\` are matched literally."
873
+ },
874
+ endsWith: {
875
+ type: import_graphql4.GraphQLString,
876
+ description: "Matches values ending with the given string. `%`, `_` and `\\` are matched literally."
877
+ },
878
+ contains: {
879
+ type: import_graphql4.GraphQLString,
880
+ description: "Matches values containing the given string. `%`, `_` and `\\` are matched literally."
881
+ },
882
+ iStartsWith: {
883
+ type: import_graphql4.GraphQLString,
884
+ description: "Case-insensitive `startsWith`."
885
+ },
886
+ iEndsWith: {
887
+ type: import_graphql4.GraphQLString,
888
+ description: "Case-insensitive `endsWith`."
889
+ },
890
+ iContains: {
891
+ type: import_graphql4.GraphQLString,
892
+ description: "Case-insensitive `contains`."
893
+ }
793
894
  },
794
895
  inArray: { type: colArr, description: `Array<${colDesc}>` },
795
896
  notInArray: { type: colArr, description: `Array<${colDesc}>` },
796
897
  isNull: { type: import_graphql4.GraphQLBoolean },
797
898
  isNotNull: { type: import_graphql4.GraphQLBoolean }
798
899
  };
799
- const orType = new import_graphql4.GraphQLInputObjectType({
800
- name: `${genericName}FilterOr`,
801
- fields: { ...baseFields }
802
- });
803
900
  const mainType = new import_graphql4.GraphQLInputObjectType({
804
901
  name: `${genericName}Filter`,
805
- fields: {
902
+ fields: () => ({
806
903
  ...baseFields,
807
904
  OR: {
808
- type: new import_graphql4.GraphQLList(new import_graphql4.GraphQLNonNull(orType))
905
+ type: new import_graphql4.GraphQLList(new import_graphql4.GraphQLNonNull(mainType)),
906
+ description: "At least one branch matches; ANDed with any sibling operators"
907
+ },
908
+ AND: {
909
+ type: new import_graphql4.GraphQLList(new import_graphql4.GraphQLNonNull(mainType)),
910
+ description: "Every branch matches"
911
+ },
912
+ NOT: {
913
+ type: mainType,
914
+ description: "Negates the nested operators"
809
915
  }
810
- }
916
+ })
811
917
  });
812
- cacheCtx.genericFilterCache.set(genericName, { main: mainType, or: orType });
918
+ cacheCtx.genericFilterCache.set(genericName, mainType);
813
919
  return mainType;
814
920
  };
815
921
  var orderMap = /* @__PURE__ */ new WeakMap();
@@ -863,19 +969,53 @@ var generateTableSelectTypeFieldsCached = (table, tableName) => {
863
969
  fieldMap.set(table, remapped);
864
970
  return remapped;
865
971
  };
866
- var generateTableOrderTypeCached = (table, tableName, typeNameMapper, cacheCtx) => {
972
+ var isFilterableRelation = (relation) => !relation.through;
973
+ var generateRelationOrderFields = (tableName, cacheCtx, typeNameMapper, columnFields, relationMap, tables) => {
974
+ const relations = relationMap?.[tableName];
975
+ if (!relations || !tables) {
976
+ return {};
977
+ }
978
+ const fields = {};
979
+ for (const [relationName, relEntry] of Object.entries(relations)) {
980
+ if (relationName in columnFields) {
981
+ continue;
982
+ }
983
+ const targetTable = tables[relEntry.targetTableName];
984
+ const relation = relEntry.relation ?? relEntry;
985
+ if (!targetTable || !(0, import_drizzle_orm3.is)(relation, import_drizzle_orm3.One) || !isFilterableRelation(relation)) {
986
+ continue;
987
+ }
988
+ fields[relationName] = {
989
+ type: generateTableOrderTypeCached(
990
+ targetTable,
991
+ relEntry.targetTableName,
992
+ typeNameMapper,
993
+ cacheCtx,
994
+ relationMap,
995
+ tables
996
+ ),
997
+ description: `Order by columns of the related ${relationName} row`
998
+ };
999
+ }
1000
+ return fields;
1001
+ };
1002
+ var generateTableOrderTypeCached = (table, tableName, typeNameMapper, cacheCtx, relationMap, tables) => {
867
1003
  if (cacheCtx.orderTypeCache.has(table)) {
868
1004
  return cacheCtx.orderTypeCache.get(table);
869
1005
  }
870
- const orderColumns = generateTableOrderCached(table);
871
1006
  const order = new import_graphql4.GraphQLInputObjectType({
872
1007
  name: `${resolveTypeName(tableName, typeNameMapper)}OrderBy`,
873
- fields: orderColumns
1008
+ fields: () => {
1009
+ const orderColumns = generateTableOrderCached(table);
1010
+ return {
1011
+ ...orderColumns,
1012
+ ...generateRelationOrderFields(tableName, cacheCtx, typeNameMapper, orderColumns, relationMap, tables)
1013
+ };
1014
+ }
874
1015
  });
875
1016
  cacheCtx.orderTypeCache.set(table, order);
876
1017
  return order;
877
1018
  };
878
- var isFilterableRelation = (relation) => !relation.through;
879
1019
  var generateListRelationFilterCached = (targetTable, targetTableName, cacheCtx, typeNameMapper, relationMap, tables) => {
880
1020
  const cached = cacheCtx.listRelationFilterCache.get(targetTableName);
881
1021
  if (cached) {
@@ -914,7 +1054,7 @@ var generateRelationFilterFields = (tableName, cacheCtx, typeNameMapper, columnF
914
1054
  }
915
1055
  const targetTable = tables[relEntry.targetTableName];
916
1056
  const relation = relEntry.relation ?? relEntry;
917
- if (!targetTable || !isFilterableRelation(relation)) {
1057
+ if (!targetTable) {
918
1058
  continue;
919
1059
  }
920
1060
  fields[relationName] = (0, import_drizzle_orm3.is)(relation, import_drizzle_orm3.One) ? {
@@ -951,16 +1091,21 @@ var generateTableFilterTypeCached = (table, tableName, cacheCtx, typeNameMapper,
951
1091
  ...generateRelationFilterFields(tableName, cacheCtx, typeNameMapper, filterColumns, relationMap, tables)
952
1092
  };
953
1093
  };
954
- const orFilters = new import_graphql4.GraphQLInputObjectType({
955
- name: `${resolveTypeName(tableName, typeNameMapper)}FiltersOr`,
956
- fields: buildFields
957
- });
958
1094
  const filters = new import_graphql4.GraphQLInputObjectType({
959
1095
  name: `${resolveTypeName(tableName, typeNameMapper)}Filters`,
960
1096
  fields: () => ({
961
1097
  ...buildFields(),
962
1098
  OR: {
963
- type: new import_graphql4.GraphQLList(new import_graphql4.GraphQLNonNull(orFilters))
1099
+ type: new import_graphql4.GraphQLList(new import_graphql4.GraphQLNonNull(filters)),
1100
+ description: "At least one branch matches; ANDed with any sibling fields"
1101
+ },
1102
+ AND: {
1103
+ type: new import_graphql4.GraphQLList(new import_graphql4.GraphQLNonNull(filters)),
1104
+ description: "Every branch matches"
1105
+ },
1106
+ NOT: {
1107
+ type: filters,
1108
+ description: "Negates the nested filters"
964
1109
  }
965
1110
  })
966
1111
  });
@@ -969,7 +1114,7 @@ var generateTableFilterTypeCached = (table, tableName, cacheCtx, typeNameMapper,
969
1114
  };
970
1115
  var generateSelectFields = (tables, tableName, relationMap, fromTableName, fromRelationName, withOrder, relationsDepthLimit, cacheCtx, typeNameMapper, usedTables = /* @__PURE__ */ new Set(), resolverFactory, currentDepth = 0, relationAggregateFactory) => {
971
1116
  const table = tables[tableName];
972
- const order = withOrder ? generateTableOrderTypeCached(table, tableName, typeNameMapper, cacheCtx) : void 0;
1117
+ const order = withOrder ? generateTableOrderTypeCached(table, tableName, typeNameMapper, cacheCtx, relationMap, tables) : void 0;
973
1118
  const filters = generateTableFilterTypeCached(table, tableName, cacheCtx, typeNameMapper, relationMap, tables);
974
1119
  const tableFields = generateTableSelectTypeFieldsCached(table, tableName);
975
1120
  const relationsForTable = relationMap[tableName];
@@ -1055,10 +1200,11 @@ var generateSelectFields = (tables, tableName, relationMap, fromTableName, fromR
1055
1200
  }
1056
1201
  const resolve = resolverFactory?.({ tableName, relationName, relEntry, isOne });
1057
1202
  if (isOne) {
1203
+ const isRequired = relation.optional === false;
1058
1204
  rawRelationFields.push([
1059
1205
  relationName,
1060
1206
  {
1061
- type: relType,
1207
+ type: isRequired ? new import_graphql4.GraphQLNonNull(relType) : relType,
1062
1208
  args: {
1063
1209
  where: { type: relSelectData.filters }
1064
1210
  },
@@ -1196,28 +1342,95 @@ var generateTableTypes = (tableName, tables, relationMap, withReturning, relatio
1196
1342
  outputs
1197
1343
  };
1198
1344
  };
1199
- var orderByEntries = (orderArgs) => Object.entries(orderArgs).sort((a, b) => (b[1]?.priority ?? 0) - (a[1]?.priority ?? 0)).filter(([, config]) => config).map(([column, config]) => [column, config.direction]);
1200
- var extractOrderBy = (table, orderArgs) => orderByEntries(orderArgs).map(
1201
- ([column, direction]) => direction === "asc" ? (0, import_drizzle_orm3.asc)((0, import_drizzle_orm3.getColumns)(table)[column]) : (0, import_drizzle_orm3.desc)((0, import_drizzle_orm3.getColumns)(table)[column])
1202
- );
1203
- var extractFiltersColumn = (column, columnName, operators) => {
1204
- if (!operators.OR?.length) {
1205
- delete operators.OR;
1345
+ var orderByEntries = (orderArgs) => Object.entries(orderArgs).sort((a, b) => (b[1]?.priority ?? 0) - (a[1]?.priority ?? 0)).filter(([, config]) => config).map(([column, config]) => {
1346
+ if (typeof config === "object" && config.direction === void 0) {
1347
+ throw new import_graphql4.GraphQLError(`ORDER BY ${column}: ordering through a relation is not supported in this query`);
1348
+ }
1349
+ return [column, config.direction, config.nulls ?? void 0];
1350
+ });
1351
+ var orderExpressions = (expr, direction, nulls, dialectColumn) => {
1352
+ const directed = direction === "asc" ? (0, import_drizzle_orm3.asc)(expr) : (0, import_drizzle_orm3.desc)(expr);
1353
+ if (!nulls) {
1354
+ return [directed];
1206
1355
  }
1207
- const entries = Object.entries(operators);
1208
- if (operators.OR) {
1209
- if (entries.length > 1) {
1210
- throw new import_graphql4.GraphQLError(`WHERE ${columnName}: Cannot specify both fields and 'OR' in column operators!`);
1356
+ const isMySql = (dialectColumn.columnType ?? "").startsWith("MySql");
1357
+ if (isMySql) {
1358
+ const nullsKey = nulls === "first" ? (0, import_drizzle_orm3.desc)(import_drizzle_orm3.sql`(${expr} is null)`) : (0, import_drizzle_orm3.asc)(import_drizzle_orm3.sql`(${expr} is null)`);
1359
+ return [nullsKey, directed];
1360
+ }
1361
+ return [nulls === "first" ? import_drizzle_orm3.sql`${directed} nulls first` : import_drizzle_orm3.sql`${directed} nulls last`];
1362
+ };
1363
+ var extendRelationOrderChain = (parentTable, relationName, relEntry, ctx, chain) => {
1364
+ const targetTable = ctx.tables[relEntry.targetTableName];
1365
+ const relation = relEntry.relation ?? relEntry;
1366
+ if (!targetTable || !(0, import_drizzle_orm3.is)(relation, import_drizzle_orm3.One) || !isFilterableRelation(relation)) {
1367
+ throw new import_graphql4.GraphQLError(`ORDER BY ${relationName}: Relation cannot be used for ordering`);
1368
+ }
1369
+ ctx.aliases ??= { n: 0 };
1370
+ const aliasedTarget = (0, import_drizzle_orm3.aliasedTable)(targetTable, `dgql_ord_${ctx.aliases.n++}`);
1371
+ const joinCondition = buildRelationJoinCondition(parentTable, relation, aliasedTarget, relationName);
1372
+ const relationWhere = relation.where ? (0, import_drizzle_orm3.relationsFilterToSQL)(relation.isReversed ? parentTable : aliasedTarget, relation.where) : void 0;
1373
+ return {
1374
+ fromParts: [...chain?.fromParts ?? [], (0, import_drizzle_orm3.getTableAsAliasSQL)(aliasedTarget)],
1375
+ conditions: [
1376
+ ...chain?.conditions ?? [],
1377
+ ...joinCondition ? [joinCondition] : [],
1378
+ ...relationWhere ? [relationWhere] : []
1379
+ ],
1380
+ table: aliasedTarget
1381
+ };
1382
+ };
1383
+ var collectOrderEntries = (table, tableKey, orderArgs, ctx, chain, out) => {
1384
+ const columns = (0, import_drizzle_orm3.getColumns)(table);
1385
+ const relations = ctx?.relationMap[tableKey];
1386
+ for (const [key, config] of Object.entries(orderArgs)) {
1387
+ if (config === null || config === void 0) {
1388
+ continue;
1211
1389
  }
1212
- const variants2 = [];
1213
- for (const variant of operators.OR) {
1214
- const extracted = extractFiltersColumn(column, columnName, variant);
1215
- if (extracted) {
1216
- variants2.push(extracted);
1217
- }
1390
+ const column = columns[key];
1391
+ if (column) {
1392
+ out.push({
1393
+ expression: chain ? import_drizzle_orm3.sql`(select ${column} from ${import_drizzle_orm3.sql.join(chain.fromParts, import_drizzle_orm3.sql`, `)} where ${(0, import_drizzle_orm3.and)(...chain.conditions)})` : column,
1394
+ column,
1395
+ direction: config.direction,
1396
+ nulls: config.nulls ?? void 0,
1397
+ priority: config.priority ?? 0
1398
+ });
1399
+ continue;
1400
+ }
1401
+ const relEntry = relations?.[key];
1402
+ if (!relEntry || !ctx) {
1403
+ throw new import_graphql4.GraphQLError(`ORDER BY ${key}: Unknown column or relation`);
1218
1404
  }
1219
- return variants2.length ? variants2.length > 1 ? (0, import_drizzle_orm3.or)(...variants2) : variants2[0] : void 0;
1405
+ const nextChain = extendRelationOrderChain(chain?.table ?? table, key, relEntry, ctx, chain);
1406
+ collectOrderEntries(nextChain.table, relEntry.targetTableName, config, ctx, nextChain, out);
1220
1407
  }
1408
+ };
1409
+ var extractOrderBy = (table, orderArgs, ctx) => {
1410
+ const entries = [];
1411
+ collectOrderEntries(table, ctx?.tableKey ?? "", orderArgs, ctx, void 0, entries);
1412
+ return entries.sort((a, b) => b.priority - a.priority).flatMap((entry) => orderExpressions(entry.expression, entry.direction, entry.nulls, entry.column));
1413
+ };
1414
+ var LIKE_ESCAPE_CHAR = "\\";
1415
+ var escapeLikeValue = (value) => value.replace(/[\\%_]/g, (char) => `\\${char}`);
1416
+ var safeLikeOps = {
1417
+ startsWith: { buildPattern: (value) => `${escapeLikeValue(value)}%`, insensitive: false },
1418
+ endsWith: { buildPattern: (value) => `%${escapeLikeValue(value)}`, insensitive: false },
1419
+ contains: { buildPattern: (value) => `%${escapeLikeValue(value)}%`, insensitive: false },
1420
+ iStartsWith: { buildPattern: (value) => `${escapeLikeValue(value)}%`, insensitive: true },
1421
+ iEndsWith: { buildPattern: (value) => `%${escapeLikeValue(value)}`, insensitive: true },
1422
+ iContains: { buildPattern: (value) => `%${escapeLikeValue(value)}%`, insensitive: true }
1423
+ };
1424
+ var safeLikeCondition = (column, pattern, insensitive) => {
1425
+ if (!insensitive) {
1426
+ return import_drizzle_orm3.sql`${column} like ${pattern} escape ${LIKE_ESCAPE_CHAR}`;
1427
+ }
1428
+ const isPg = (column.columnType ?? "").startsWith("Pg");
1429
+ return isPg ? import_drizzle_orm3.sql`${column} ilike ${pattern} escape ${LIKE_ESCAPE_CHAR}` : import_drizzle_orm3.sql`lower(${column}) like ${pattern.toLowerCase()} escape ${LIKE_ESCAPE_CHAR}`;
1430
+ };
1431
+ var extractFiltersColumn = (column, columnName, operators) => {
1432
+ const { OR, AND, NOT, ...restOperators } = operators;
1433
+ const entries = Object.entries(restOperators);
1221
1434
  const singleValueOps = { eq: import_drizzle_orm3.eq, ne: import_drizzle_orm3.ne, gt: import_drizzle_orm3.gt, gte: import_drizzle_orm3.gte, lt: import_drizzle_orm3.lt, lte: import_drizzle_orm3.lte };
1222
1435
  const stringValueOps = { like: import_drizzle_orm3.like, notLike: import_drizzle_orm3.notLike, ilike: import_drizzle_orm3.ilike, notIlike: import_drizzle_orm3.notIlike };
1223
1436
  const arrayValueOps = { inArray: import_drizzle_orm3.inArray, notInArray: import_drizzle_orm3.notInArray };
@@ -1232,6 +1445,9 @@ var extractFiltersColumn = (column, columnName, operators) => {
1232
1445
  variants.push(singleValueOps[operatorName](column, singleValue));
1233
1446
  } else if (operatorName in stringValueOps) {
1234
1447
  variants.push(stringValueOps[operatorName](column, operatorValue));
1448
+ } else if (operatorName in safeLikeOps) {
1449
+ const { buildPattern, insensitive } = safeLikeOps[operatorName];
1450
+ variants.push(safeLikeCondition(column, buildPattern(operatorValue), insensitive));
1235
1451
  } else if (operatorName in arrayValueOps) {
1236
1452
  if (!operatorValue.length) {
1237
1453
  throw new import_graphql4.GraphQLError(`WHERE ${columnName}: Unable to use operator ${operatorName} with an empty array!`);
@@ -1240,6 +1456,34 @@ var extractFiltersColumn = (column, columnName, operators) => {
1240
1456
  variants.push(arrayValueOps[operatorName](column, arrayValue));
1241
1457
  } else if (operatorName in nullableOps) {
1242
1458
  variants.push(nullableOps[operatorName](column));
1459
+ } else {
1460
+ throw new import_graphql4.GraphQLError(`WHERE ${columnName}: Unknown operator: ${operatorName}`);
1461
+ }
1462
+ }
1463
+ if (AND?.length) {
1464
+ for (const variant of AND) {
1465
+ const extracted = extractFiltersColumn(column, columnName, variant);
1466
+ if (extracted) {
1467
+ variants.push(extracted);
1468
+ }
1469
+ }
1470
+ }
1471
+ if (NOT) {
1472
+ const extracted = extractFiltersColumn(column, columnName, NOT);
1473
+ if (extracted) {
1474
+ variants.push((0, import_drizzle_orm3.not)(extracted));
1475
+ }
1476
+ }
1477
+ if (OR?.length) {
1478
+ const orVariants = [];
1479
+ for (const variant of OR) {
1480
+ const extracted = extractFiltersColumn(column, columnName, variant);
1481
+ if (extracted) {
1482
+ orVariants.push(extracted);
1483
+ }
1484
+ }
1485
+ if (orVariants.length) {
1486
+ variants.push(orVariants.length > 1 ? (0, import_drizzle_orm3.or)(...orVariants) : orVariants[0]);
1243
1487
  }
1244
1488
  }
1245
1489
  return variants.length ? variants.length > 1 ? (0, import_drizzle_orm3.and)(...variants) : variants[0] : void 0;
@@ -1264,39 +1508,105 @@ var buildRelationJoinCondition = (parentTable, relation, aliasedTarget, relation
1264
1508
  }
1265
1509
  return conditions.length > 1 ? (0, import_drizzle_orm3.and)(...conditions) : conditions[0];
1266
1510
  };
1511
+ var resolveJunctionColumn = (aliasedThrough, junctionRef, relationName) => {
1512
+ const throughColumns = (0, import_drizzle_orm3.getColumns)(aliasedThrough);
1513
+ const key = junctionRef?._?.key;
1514
+ const byKey = key ? throughColumns[key] : void 0;
1515
+ if (byKey) {
1516
+ return byKey;
1517
+ }
1518
+ const columnName = junctionRef?._?.column?.name;
1519
+ const byName = columnName ? Object.values(throughColumns).find((c) => c.name === columnName) : void 0;
1520
+ if (!byName) {
1521
+ throw new import_graphql4.GraphQLError(`WHERE ${relationName}: Relation cannot be used as a filter`);
1522
+ }
1523
+ return byName;
1524
+ };
1525
+ var buildThroughJoinConditions = (parentTable, relation, aliasedThrough, aliasedTarget, relationName) => {
1526
+ const sourceColumns = relation.sourceColumns;
1527
+ const targetColumns = relation.targetColumns;
1528
+ const through = relation.through;
1529
+ if (!sourceColumns?.length || !targetColumns?.length || through?.source.length !== sourceColumns.length || through.target.length !== targetColumns.length) {
1530
+ throw new import_graphql4.GraphQLError(`WHERE ${relationName}: Relation cannot be used as a filter`);
1531
+ }
1532
+ const parentColumns = Object.values((0, import_drizzle_orm3.getColumns)(parentTable));
1533
+ const targetTableColumns = Object.values((0, import_drizzle_orm3.getColumns)(aliasedTarget));
1534
+ const correlationConditions = [];
1535
+ for (let i = 0; i < sourceColumns.length; i++) {
1536
+ const localColumn = parentColumns.find((c) => c.name === sourceColumns[i].name);
1537
+ if (!localColumn) {
1538
+ throw new import_graphql4.GraphQLError(`WHERE ${relationName}: Relation cannot be used as a filter`);
1539
+ }
1540
+ correlationConditions.push((0, import_drizzle_orm3.eq)(localColumn, resolveJunctionColumn(aliasedThrough, through.source[i], relationName)));
1541
+ }
1542
+ const junctionJoinConditions = [];
1543
+ for (let i = 0; i < targetColumns.length; i++) {
1544
+ const foreignColumn = targetTableColumns.find((c) => c.name === targetColumns[i].name);
1545
+ if (!foreignColumn) {
1546
+ throw new import_graphql4.GraphQLError(`WHERE ${relationName}: Relation cannot be used as a filter`);
1547
+ }
1548
+ junctionJoinConditions.push(
1549
+ (0, import_drizzle_orm3.eq)(resolveJunctionColumn(aliasedThrough, through.target[i], relationName), foreignColumn)
1550
+ );
1551
+ }
1552
+ return {
1553
+ correlation: correlationConditions.length > 1 ? (0, import_drizzle_orm3.and)(...correlationConditions) : correlationConditions[0],
1554
+ junctionJoin: junctionJoinConditions.length > 1 ? (0, import_drizzle_orm3.and)(...junctionJoinConditions) : junctionJoinConditions[0]
1555
+ };
1556
+ };
1267
1557
  var buildRelationExists = (parentTable, relationName, relEntry, innerFilters, mode, ctx) => {
1268
1558
  const { targetTableName } = relEntry;
1269
1559
  const targetTable = ctx.tables[targetTableName];
1270
1560
  const relation = relEntry.relation ?? relEntry;
1271
- if (!targetTable || !isFilterableRelation(relation)) {
1561
+ if (!targetTable) {
1272
1562
  throw new import_graphql4.GraphQLError(`WHERE ${relationName}: Relation cannot be used as a filter`);
1273
1563
  }
1274
1564
  ctx.aliases ??= { n: 0 };
1275
1565
  const aliases = ctx.aliases;
1276
1566
  const aliasedTarget = (0, import_drizzle_orm3.aliasedTable)(targetTable, `dgql_rel_${aliases.n++}`);
1277
- const joinCondition = buildRelationJoinCondition(parentTable, relation, aliasedTarget, relationName);
1567
+ const throughTable = relation.throughTable;
1568
+ let fromClause;
1569
+ let joinCondition;
1570
+ if (throughTable) {
1571
+ const aliasedThrough = (0, import_drizzle_orm3.aliasedTable)(throughTable, `dgql_rel_${aliases.n++}`);
1572
+ const { correlation, junctionJoin } = buildThroughJoinConditions(
1573
+ parentTable,
1574
+ relation,
1575
+ aliasedThrough,
1576
+ aliasedTarget,
1577
+ relationName
1578
+ );
1579
+ fromClause = import_drizzle_orm3.sql`${(0, import_drizzle_orm3.getTableAsAliasSQL)(aliasedTarget)} inner join ${(0, import_drizzle_orm3.getTableAsAliasSQL)(aliasedThrough)} on ${junctionJoin}`;
1580
+ joinCondition = correlation;
1581
+ } else {
1582
+ fromClause = (0, import_drizzle_orm3.getTableAsAliasSQL)(aliasedTarget);
1583
+ joinCondition = buildRelationJoinCondition(parentTable, relation, aliasedTarget, relationName);
1584
+ }
1278
1585
  const relationWhere = relation.where ? (0, import_drizzle_orm3.relationsFilterToSQL)(relation.isReversed ? parentTable : aliasedTarget, relation.where) : void 0;
1279
1586
  const inner = innerFilters ? extractFilters(aliasedTarget, targetTableName, innerFilters, { ...ctx, tableKey: targetTableName, aliases }) : void 0;
1280
1587
  if (mode === "every") {
1281
1588
  if (!inner) {
1282
1589
  return void 0;
1283
1590
  }
1284
- return import_drizzle_orm3.sql`not exists (select 1 from ${(0, import_drizzle_orm3.getTableAsAliasSQL)(aliasedTarget)} where ${(0, import_drizzle_orm3.and)(joinCondition, relationWhere, (0, import_drizzle_orm3.not)(inner))})`;
1591
+ return import_drizzle_orm3.sql`not exists (select 1 from ${fromClause} where ${(0, import_drizzle_orm3.and)(joinCondition, relationWhere, (0, import_drizzle_orm3.not)(inner))})`;
1285
1592
  }
1286
1593
  const condition = (0, import_drizzle_orm3.and)(joinCondition, relationWhere, inner);
1287
- return mode === "none" ? import_drizzle_orm3.sql`not exists (select 1 from ${(0, import_drizzle_orm3.getTableAsAliasSQL)(aliasedTarget)} where ${condition})` : import_drizzle_orm3.sql`exists (select 1 from ${(0, import_drizzle_orm3.getTableAsAliasSQL)(aliasedTarget)} where ${condition})`;
1594
+ return mode === "none" ? import_drizzle_orm3.sql`not exists (select 1 from ${fromClause} where ${condition})` : import_drizzle_orm3.sql`exists (select 1 from ${fromClause} where ${condition})`;
1288
1595
  };
1289
1596
  var extractRelationFilter = (parentTable, relationName, relEntry, value, ctx) => {
1290
1597
  const relation = relEntry.relation ?? relEntry;
1291
1598
  if ((0, import_drizzle_orm3.is)(relation, import_drizzle_orm3.One)) {
1292
1599
  return buildRelationExists(parentTable, relationName, relEntry, value, "some", ctx);
1293
1600
  }
1601
+ const relationMatchModes = ["some", "none", "every"];
1294
1602
  const variants = [];
1295
- for (const mode of ["some", "none", "every"]) {
1296
- const inner = value[mode];
1603
+ for (const [mode, inner] of Object.entries(value)) {
1297
1604
  if (inner === void 0 || inner === null) {
1298
1605
  continue;
1299
1606
  }
1607
+ if (!relationMatchModes.includes(mode)) {
1608
+ throw new import_graphql4.GraphQLError(`WHERE ${relationName}: Unknown relation filter key: ${mode}`);
1609
+ }
1300
1610
  const extracted = buildRelationExists(parentTable, relationName, relEntry, inner, mode, ctx);
1301
1611
  if (extracted) {
1302
1612
  variants.push(extracted);
@@ -1305,26 +1615,8 @@ var extractRelationFilter = (parentTable, relationName, relEntry, value, ctx) =>
1305
1615
  return variants.length ? variants.length > 1 ? (0, import_drizzle_orm3.and)(...variants) : variants[0] : void 0;
1306
1616
  };
1307
1617
  var extractFilters = (table, tableName, filters, relationCtx) => {
1308
- if (!filters.OR?.length) {
1309
- delete filters.OR;
1310
- }
1311
- const entries = Object.entries(filters);
1312
- if (!entries.length) {
1313
- return;
1314
- }
1315
- if (filters.OR) {
1316
- if (entries.length > 1) {
1317
- throw new import_graphql4.GraphQLError(`WHERE ${tableName}: Cannot specify both fields and 'OR' in table filters!`);
1318
- }
1319
- const variants2 = [];
1320
- for (const variant of filters.OR) {
1321
- const extracted = extractFilters(table, tableName, variant, relationCtx);
1322
- if (extracted) {
1323
- variants2.push(extracted);
1324
- }
1325
- }
1326
- return variants2.length ? variants2.length > 1 ? (0, import_drizzle_orm3.or)(...variants2) : variants2[0] : void 0;
1327
- }
1618
+ const { OR, AND, NOT, ...fieldFilters } = filters;
1619
+ const entries = Object.entries(fieldFilters);
1328
1620
  const columns = (0, import_drizzle_orm3.getColumns)(table);
1329
1621
  const relations = relationCtx?.relationMap[relationCtx.tableKey];
1330
1622
  const variants = [];
@@ -1333,11 +1625,40 @@ var extractFilters = (table, tableName, filters, relationCtx) => {
1333
1625
  continue;
1334
1626
  }
1335
1627
  const column = columns[fieldName];
1336
- const extracted = column ? extractFiltersColumn(column, fieldName, operators) : relations?.[fieldName] && relationCtx ? extractRelationFilter(table, fieldName, relations[fieldName], operators, relationCtx) : void 0;
1628
+ if (!column && !(relations?.[fieldName] && relationCtx)) {
1629
+ throw new import_graphql4.GraphQLError(`WHERE ${tableName}: Unknown filter key: ${fieldName}`);
1630
+ }
1631
+ const extracted = column ? extractFiltersColumn(column, fieldName, operators) : extractRelationFilter(table, fieldName, relations[fieldName], operators, relationCtx);
1337
1632
  if (extracted) {
1338
1633
  variants.push(extracted);
1339
1634
  }
1340
1635
  }
1636
+ if (AND?.length) {
1637
+ for (const variant of AND) {
1638
+ const extracted = extractFilters(table, tableName, variant, relationCtx);
1639
+ if (extracted) {
1640
+ variants.push(extracted);
1641
+ }
1642
+ }
1643
+ }
1644
+ if (NOT) {
1645
+ const extracted = extractFilters(table, tableName, NOT, relationCtx);
1646
+ if (extracted) {
1647
+ variants.push((0, import_drizzle_orm3.not)(extracted));
1648
+ }
1649
+ }
1650
+ if (OR?.length) {
1651
+ const orVariants = [];
1652
+ for (const variant of OR) {
1653
+ const extracted = extractFilters(table, tableName, variant, relationCtx);
1654
+ if (extracted) {
1655
+ orVariants.push(extracted);
1656
+ }
1657
+ }
1658
+ if (orVariants.length) {
1659
+ variants.push(orVariants.length > 1 ? (0, import_drizzle_orm3.or)(...orVariants) : orVariants[0]);
1660
+ }
1661
+ }
1341
1662
  return variants.length ? variants.length > 1 ? (0, import_drizzle_orm3.and)(...variants) : variants[0] : void 0;
1342
1663
  };
1343
1664
  var extractRelationsParamsInner = (relationMap, tables, tableName, typeName, originField, typeNameMapper, _isInitial = false, filterCtx) => {
@@ -1379,7 +1700,7 @@ var extractRelationsParamsInner = (relationMap, tables, tableName, typeName, ori
1379
1700
  } : void 0;
1380
1701
  const hasPagination = offset != null || limit != null;
1381
1702
  const pkNames = targetPkNames ?? [];
1382
- thisRecord.orderBy = relationArgs?.orderBy ? (aliasedTable2) => extractOrderBy(aliasedTable2, relationArgs.orderBy) : hasPagination && pkNames.length ? (aliasedTable2) => primaryKeyOrderExprs(aliasedTable2, pkNames) : void 0;
1703
+ thisRecord.orderBy = relationArgs?.orderBy ? (aliasedTable2) => extractOrderBy(aliasedTable2, relationArgs.orderBy, relationFilterCtx(filterCtx, targetTableName)) : hasPagination && pkNames.length ? (aliasedTable2) => primaryKeyOrderExprs(aliasedTable2, pkNames) : void 0;
1383
1704
  thisRecord.offset = offset;
1384
1705
  thisRecord.limit = limit;
1385
1706
  const relWith = relationField ? extractRelationsParamsInner(
@@ -1620,7 +1941,9 @@ var selectDistinctKeys = async (params) => {
1620
1941
  }
1621
1942
  const orderEntries = orderBy ? orderByEntries(orderBy) : [];
1622
1943
  const windowOrder = [
1623
- ...orderEntries.map(([column, direction]) => direction === "asc" ? (0, import_drizzle_orm3.asc)(cols[column]) : (0, import_drizzle_orm3.desc)(cols[column])),
1944
+ ...orderEntries.flatMap(
1945
+ ([column, direction, nulls]) => orderExpressions(cols[column], direction, nulls, cols[column])
1946
+ ),
1624
1947
  ...primaryKeyOrderExprs(table, pkNames)
1625
1948
  ];
1626
1949
  const rowNumber = import_drizzle_orm3.sql`row_number() over (partition by ${import_drizzle_orm3.sql.join(partitionCols, import_drizzle_orm3.sql`, `)} order by ${import_drizzle_orm3.sql.join(
@@ -1629,7 +1952,9 @@ var selectDistinctKeys = async (params) => {
1629
1952
  )})`.as(DISTINCT_RN);
1630
1953
  const sub = db.select({ ...cols, [DISTINCT_RN]: rowNumber }).from(table).where(where).as("__dgql_distinct");
1631
1954
  const outerOrder = [
1632
- ...orderEntries.map(([column, direction]) => direction === "asc" ? (0, import_drizzle_orm3.asc)(sub[column]) : (0, import_drizzle_orm3.desc)(sub[column])),
1955
+ ...orderEntries.flatMap(
1956
+ ([column, direction, nulls]) => orderExpressions(sub[column], direction, nulls, cols[column])
1957
+ ),
1633
1958
  ...pkNames.filter((name) => sub[name]).map((name) => (0, import_drizzle_orm3.asc)(sub[name]))
1634
1959
  ];
1635
1960
  let query = db.select(Object.fromEntries(pkNames.map((name) => [name, sub[name]]))).from(sub).where((0, import_drizzle_orm3.eq)(sub[DISTINCT_RN], 1)).orderBy(...outerOrder);
@@ -1720,6 +2045,7 @@ var computeResolverFieldNames = (tableName, typeNameMapper, prefixes, suffixes)
1720
2045
  const listFieldName = (mapped?.plural ?? uncapitalize(tableName)) + suffixes.list;
1721
2046
  const singleFieldName = mapped?.singular ?? uncapitalize(tableName) + suffixes.single;
1722
2047
  const aggregateFieldName = `${mapped?.plural ?? uncapitalize(tableName)}Aggregate`;
2048
+ const groupByFieldName = `${mapped?.plural ?? uncapitalize(tableName)}GroupBy`;
1723
2049
  const createArrayFieldName = `${prefixes.insert}${mapped ? capitalize(mapped.plural) : capitalize(tableName)}`;
1724
2050
  const createSingleFieldName = mapped ? `${prefixes.insert}${capitalize(mapped.singular)}` : `${prefixes.insert}${capitalize(tableName)}${suffixes.single}`;
1725
2051
  const upsertPrefix = prefixes.upsert ?? "upsert";
@@ -1727,19 +2053,38 @@ var computeResolverFieldNames = (tableName, typeNameMapper, prefixes, suffixes)
1727
2053
  const upsertSingleFieldName = mapped ? `${upsertPrefix}${capitalize(mapped.singular)}` : `${upsertPrefix}${capitalize(tableName)}${suffixes.single}`;
1728
2054
  const updateFieldName = `${prefixes.update}${mapped ? capitalize(mapped.singular) : capitalize(tableName)}`;
1729
2055
  const deleteFieldName = `${prefixes.delete}${mapped ? capitalize(mapped.singular) : capitalize(tableName)}`;
2056
+ const writeSingleSuffix = suffixes.single === "" ? "Single" : suffixes.single;
2057
+ const updateSingleFieldName = `${updateFieldName}${writeSingleSuffix}`;
2058
+ const deleteSingleFieldName = `${deleteFieldName}${writeSingleSuffix}`;
1730
2059
  return {
1731
2060
  typeName,
1732
2061
  listFieldName,
1733
2062
  singleFieldName,
1734
2063
  aggregateFieldName,
2064
+ groupByFieldName,
1735
2065
  createArrayFieldName,
1736
2066
  createSingleFieldName,
1737
2067
  upsertArrayFieldName,
1738
2068
  upsertSingleFieldName,
1739
2069
  updateFieldName,
1740
- deleteFieldName
2070
+ updateSingleFieldName,
2071
+ deleteFieldName,
2072
+ deleteSingleFieldName
1741
2073
  };
1742
2074
  };
2075
+ var extractRequiredFilters = (table, tableName, where, fieldName, relationCtx) => {
2076
+ const filters = where ? extractFilters(table, tableName, where, relationCtx) : void 0;
2077
+ if (!filters) {
2078
+ throw new import_graphql4.GraphQLError(`${fieldName} requires a 'where' argument with at least one filter!`);
2079
+ }
2080
+ return filters;
2081
+ };
2082
+ var assertSingleMatch = async (executor, table, filters, fieldName) => {
2083
+ const matched = await executor.select({ found: import_drizzle_orm3.sql`1` }).from(table).where(filters).limit(2);
2084
+ if (matched.length > 1) {
2085
+ throw new import_graphql4.GraphQLError(`${fieldName}: 'where' matched more than one row \u2014 nothing was written!`);
2086
+ }
2087
+ };
1743
2088
  var selectArrayArgs = (orderArgs, filterArgs, distinctEnum) => ({
1744
2089
  offset: { type: import_graphql4.GraphQLInt },
1745
2090
  limit: { type: import_graphql4.GraphQLInt },
@@ -1798,9 +2143,9 @@ var runRelationalSelect = async (opts) => {
1798
2143
  // drizzle-orm v1 RQB calls orderBy/where with the aliased table proxy — use it
1799
2144
  // directly so column refs match the CTE alias.
1800
2145
  orderBy: distinctKeys ? (aliasedTable2) => [
1801
- ...orderBy ? extractOrderBy(aliasedTable2, orderBy) : [],
2146
+ ...orderBy ? extractOrderBy(aliasedTable2, orderBy, relationFilterCtx(filterCtx, tableName)) : [],
1802
2147
  ...primaryKeyOrderExprs(aliasedTable2, pkNames)
1803
- ] : orderBy ? (aliasedTable2) => extractOrderBy(aliasedTable2, orderBy) : needsDefaultOrder && pkNames?.length ? (aliasedTable2) => primaryKeyOrderExprs(aliasedTable2, pkNames) : void 0,
2148
+ ] : orderBy ? (aliasedTable2) => extractOrderBy(aliasedTable2, orderBy, relationFilterCtx(filterCtx, tableName)) : needsDefaultOrder && pkNames?.length ? (aliasedTable2) => primaryKeyOrderExprs(aliasedTable2, pkNames) : void 0,
1804
2149
  where: distinctKeys ? { RAW: (aliasedTable2) => primaryKeyRestriction(aliasedTable2, pkNames, distinctKeys) } : where ? {
1805
2150
  RAW: (aliasedTable2) => extractFilters(aliasedTable2, tableName, where, relationFilterCtx(filterCtx, tableName))
1806
2151
  } : void 0,
@@ -2006,9 +2351,9 @@ var aggregateTarget = (table, tableName, typeName) => {
2006
2351
  )
2007
2352
  };
2008
2353
  };
2009
- var parseAggregateRequest = (info, target) => {
2354
+ var parseAggregateRequest = (info, target, rootTypeName = `${target.typeName}Aggregate`) => {
2010
2355
  const parsedInfo = (0, import_graphql_parse_resolve_info.parseResolveInfo)(info, { deep: true });
2011
- const selectionTree = parsedInfo.fieldsByTypeName[`${target.typeName}Aggregate`] ?? {};
2356
+ const selectionTree = parsedInfo.fieldsByTypeName[rootTypeName] ?? {};
2012
2357
  const request = {
2013
2358
  count: false,
2014
2359
  ops: { avg: [], sum: [], min: [], max: [], countNonNull: [], countDistinct: [] },
@@ -2146,6 +2491,184 @@ var createRelationAggregateFactory = (db, tables, cacheCtx, typeNameMapper, filt
2146
2491
  return { type, resolve };
2147
2492
  };
2148
2493
  };
2494
+ var GROUP_COL = `group${SEP}`;
2495
+ var HAVING_OPS = { eq: import_drizzle_orm4.eq, ne: import_drizzle_orm4.ne, gt: import_drizzle_orm4.gt, gte: import_drizzle_orm4.gte, lt: import_drizzle_orm4.lt, lte: import_drizzle_orm4.lte };
2496
+ var groupableColumns = (table, tableName) => {
2497
+ const { orderable } = classifyAggregateColumns(table, tableName);
2498
+ const groupable = { ...orderable };
2499
+ for (const [columnName, column] of Object.entries((0, import_drizzle_orm4.getColumns)(table))) {
2500
+ if (groupable[columnName]) {
2501
+ continue;
2502
+ }
2503
+ const { type: dataType } = (0, import_drizzle_orm4.extractExtendedColumnType)(column);
2504
+ if (dataType !== "boolean") {
2505
+ continue;
2506
+ }
2507
+ groupable[columnName] = {
2508
+ column,
2509
+ converted: drizzleColumnToGraphQLType(column, columnName, tableName, true, false, false)
2510
+ };
2511
+ }
2512
+ return groupable;
2513
+ };
2514
+ var generateGroupByEnum = (table, tableName, typeName) => {
2515
+ const groupable = groupableColumns(table, tableName);
2516
+ return generateColumnEnum(
2517
+ table,
2518
+ `${typeName}GroupByColumn`,
2519
+ `Columns of ${typeName} that a query can group by`,
2520
+ (_column, columnName) => Boolean(groupable[columnName])
2521
+ );
2522
+ };
2523
+ var aggregateNumberFilter = new import_graphql5.GraphQLInputObjectType({
2524
+ name: "AggregateNumberFilter",
2525
+ description: "Compares an aggregated value. Several operators in one filter are ANDed together.",
2526
+ fields: Object.fromEntries(Object.keys(HAVING_OPS).map((op) => [op, { type: import_graphql5.GraphQLFloat }]))
2527
+ });
2528
+ var generateHavingInput = (table, tableName, typeName) => {
2529
+ const { numeric, orderable, all } = classifyAggregateColumns(table, tableName);
2530
+ const fields = {
2531
+ count: { type: aggregateNumberFilter, description: "Filters groups by how many rows they contain" }
2532
+ };
2533
+ const opColumns = {
2534
+ avg: numeric,
2535
+ sum: numeric,
2536
+ min: numeric,
2537
+ max: numeric,
2538
+ countNonNull: all,
2539
+ countDistinct: orderable
2540
+ };
2541
+ for (const [op, columns] of Object.entries(opColumns)) {
2542
+ const columnNames = Object.keys(columns);
2543
+ if (!columnNames.length) {
2544
+ continue;
2545
+ }
2546
+ fields[op] = {
2547
+ type: new import_graphql5.GraphQLInputObjectType({
2548
+ name: `${typeName}${capitalize(op)}Having`,
2549
+ fields: Object.fromEntries(columnNames.map((columnName) => [columnName, { type: aggregateNumberFilter }]))
2550
+ })
2551
+ };
2552
+ }
2553
+ return new import_graphql5.GraphQLInputObjectType({
2554
+ name: `${typeName}Having`,
2555
+ description: `Filters ${typeName} groups by their aggregated values`,
2556
+ fields
2557
+ });
2558
+ };
2559
+ var generateGroupByType = (table, tableName, typeName, cacheCtx) => {
2560
+ const groupable = groupableColumns(table, tableName);
2561
+ if (!Object.keys(groupable).length) {
2562
+ return void 0;
2563
+ }
2564
+ const keysType = new import_graphql5.GraphQLObjectType({
2565
+ name: `${typeName}GroupKeys`,
2566
+ description: `The grouped column values of one ${typeName} group. A column the query did not group by is null.`,
2567
+ fields: Object.fromEntries(
2568
+ Object.entries(groupable).map(([columnName, { converted }]) => [
2569
+ columnName,
2570
+ { type: converted.type, description: converted.description }
2571
+ ])
2572
+ )
2573
+ });
2574
+ const aggregateType = generateAggregateTypes(table, tableName, typeName, cacheCtx);
2575
+ const aggregateFields = Object.fromEntries(
2576
+ Object.values(aggregateType.getFields()).map((field) => [
2577
+ field.name,
2578
+ { type: field.type, description: field.description ?? void 0 }
2579
+ ])
2580
+ );
2581
+ return new import_graphql5.GraphQLObjectType({
2582
+ name: `${typeName}GroupBy`,
2583
+ fields: { group: { type: new import_graphql5.GraphQLNonNull(keysType) }, ...aggregateFields }
2584
+ });
2585
+ };
2586
+ var havingComparisons = (expression, filter) => Object.entries(filter).filter(([op, value]) => value != null && op in HAVING_OPS).map(([op, value]) => HAVING_OPS[op](expression, value));
2587
+ var buildHavingCondition = (having, columns) => {
2588
+ if (!having) {
2589
+ return void 0;
2590
+ }
2591
+ const conditions = [];
2592
+ for (const [key, value] of Object.entries(having)) {
2593
+ if (value == null) {
2594
+ continue;
2595
+ }
2596
+ if (key === "count") {
2597
+ conditions.push(...havingComparisons((0, import_drizzle_orm4.count)(), value));
2598
+ continue;
2599
+ }
2600
+ if (!AGGREGATE_OPS.includes(key)) {
2601
+ continue;
2602
+ }
2603
+ for (const [columnName, filter] of Object.entries(value)) {
2604
+ const column = columns[columnName];
2605
+ if (!column || filter == null) {
2606
+ continue;
2607
+ }
2608
+ conditions.push(...havingComparisons(OP_FNS[key](column), filter));
2609
+ }
2610
+ }
2611
+ return conditions.length ? (0, import_drizzle_orm4.and)(...conditions) : void 0;
2612
+ };
2613
+ var generateGroupBy = (db, tableName, table, typeName, fieldName, filterArgs, groupByEnum, havingInput, filterCtx) => {
2614
+ const target = aggregateTarget(table, tableName, typeName);
2615
+ const groupable = groupableColumns(table, tableName);
2616
+ const columns = (0, import_drizzle_orm4.getColumns)(table);
2617
+ const rootTypeName = `${typeName}GroupBy`;
2618
+ const queryArgs = {
2619
+ groupBy: {
2620
+ type: new import_graphql5.GraphQLNonNull(new import_graphql5.GraphQLList(new import_graphql5.GraphQLNonNull(groupByEnum))),
2621
+ description: "Columns to group by. One result row per distinct combination of their values."
2622
+ },
2623
+ where: { type: filterArgs, description: "Filters the rows before they are grouped." },
2624
+ having: { type: havingInput, description: "Filters the groups after they are aggregated." }
2625
+ };
2626
+ return {
2627
+ name: fieldName,
2628
+ resolver: async (_source, args, context, info) => {
2629
+ try {
2630
+ const requestedKeys = [...new Set(args.groupBy ?? [])];
2631
+ if (!requestedKeys.length) {
2632
+ throw new import_graphql5.GraphQLError("At least one column to group by is required!");
2633
+ }
2634
+ const keyColumns = requestedKeys.map((columnName) => {
2635
+ const groupableColumn = groupable[columnName];
2636
+ if (!groupableColumn) {
2637
+ throw new import_graphql5.GraphQLError(`Cannot group ${typeName} by ${columnName}!`);
2638
+ }
2639
+ return [columnName, groupableColumn.column];
2640
+ });
2641
+ const request = parseAggregateRequest(info, target, rootTypeName);
2642
+ const selection = {
2643
+ ...Object.fromEntries(keyColumns.map(([columnName, column]) => [`${GROUP_COL}${columnName}`, column])),
2644
+ ...request.selection
2645
+ };
2646
+ let query = resolveExecutor(db, context).select(selection).from(table);
2647
+ if (args.where) {
2648
+ query = query.where(extractFilters(table, tableName, args.where, relationFilterCtx(filterCtx, tableName)));
2649
+ }
2650
+ query = query.groupBy(...keyColumns.map(([, column]) => column));
2651
+ const havingCondition = buildHavingCondition(args.having, columns);
2652
+ if (havingCondition) {
2653
+ query = query.having(havingCondition);
2654
+ }
2655
+ const rows = await query;
2656
+ return rows.map((row) => {
2657
+ const group = {};
2658
+ for (const [columnName, column] of keyColumns) {
2659
+ const value = row[`${GROUP_COL}${columnName}`];
2660
+ const decoded = typeof value === "string" && target.dateTimeColumns.has(columnName) ? parseDriverDateTime(value) : value;
2661
+ group[columnName] = value == null ? null : remapToGraphQLCore(columnName, decoded, tableName, column);
2662
+ }
2663
+ return { group, ...assembleAggregateRow(row, request, target) };
2664
+ });
2665
+ } catch (e) {
2666
+ throw toGraphQLError(e);
2667
+ }
2668
+ },
2669
+ args: queryArgs
2670
+ };
2671
+ };
2149
2672
 
2150
2673
  // src/util/builders/mysql.ts
2151
2674
  var generateSelectArray = (db, tableName, tables, relationMap, orderArgs, filterArgs, fieldName, typeName, typeNameMapper, filterCtx, distinctEnabled = true) => {
@@ -2313,13 +2836,13 @@ var generateUpsert = (db, table, baseType, onConflictType, fieldName, single) =>
2313
2836
  args: queryArgs
2314
2837
  };
2315
2838
  };
2316
- var generateUpdate = (db, tableName, table, setArgs, filterArgs, fieldName, filterCtx) => {
2839
+ var generateUpdate = (db, tableName, table, setArgs, filterArgs, fieldName, single, requireWhere, filterCtx) => {
2317
2840
  const queryArgs = {
2318
2841
  set: {
2319
2842
  type: new import_graphql6.GraphQLNonNull(setArgs)
2320
2843
  },
2321
2844
  where: {
2322
- type: filterArgs
2845
+ type: single || requireWhere ? new import_graphql6.GraphQLNonNull(filterArgs) : filterArgs
2323
2846
  }
2324
2847
  };
2325
2848
  return {
@@ -2331,10 +2854,14 @@ var generateUpdate = (db, tableName, table, setArgs, filterArgs, fieldName, filt
2331
2854
  if (!Object.keys(input).length) {
2332
2855
  throw new import_graphql6.GraphQLError("Unable to update with no values specified!");
2333
2856
  }
2857
+ const relationCtx = relationFilterCtx(filterCtx, tableName);
2858
+ const filters = single || requireWhere ? extractRequiredFilters(table, tableName, where, fieldName, relationCtx) : where ? extractFilters(table, tableName, where, relationCtx) : void 0;
2334
2859
  const executor = resolveExecutor(db, context);
2860
+ if (single) {
2861
+ await assertSingleMatch(executor, table, filters, fieldName);
2862
+ }
2335
2863
  let query = executor.update(table).set(input);
2336
- if (where) {
2337
- const filters = extractFilters(table, tableName, where, relationFilterCtx(filterCtx, tableName));
2864
+ if (filters) {
2338
2865
  query = query.where(filters);
2339
2866
  }
2340
2867
  await query;
@@ -2346,10 +2873,10 @@ var generateUpdate = (db, tableName, table, setArgs, filterArgs, fieldName, filt
2346
2873
  args: queryArgs
2347
2874
  };
2348
2875
  };
2349
- var generateDelete = (db, tableName, table, filterArgs, fieldName, filterCtx) => {
2876
+ var generateDelete = (db, tableName, table, filterArgs, fieldName, single, requireWhere, filterCtx) => {
2350
2877
  const queryArgs = {
2351
2878
  where: {
2352
- type: filterArgs
2879
+ type: single || requireWhere ? new import_graphql6.GraphQLNonNull(filterArgs) : filterArgs
2353
2880
  }
2354
2881
  };
2355
2882
  return {
@@ -2357,10 +2884,14 @@ var generateDelete = (db, tableName, table, filterArgs, fieldName, filterCtx) =>
2357
2884
  resolver: async (_source, args, context, _info) => {
2358
2885
  try {
2359
2886
  const { where } = args;
2887
+ const relationCtx = relationFilterCtx(filterCtx, tableName);
2888
+ const filters = single || requireWhere ? extractRequiredFilters(table, tableName, where, fieldName, relationCtx) : where ? extractFilters(table, tableName, where, relationCtx) : void 0;
2360
2889
  const executor = resolveExecutor(db, context);
2890
+ if (single) {
2891
+ await assertSingleMatch(executor, table, filters, fieldName);
2892
+ }
2361
2893
  let query = executor.delete(table);
2362
- if (where) {
2363
- const filters = extractFilters(table, tableName, where, relationFilterCtx(filterCtx, tableName));
2894
+ if (filters) {
2364
2895
  query = query.where(filters);
2365
2896
  }
2366
2897
  await query;
@@ -2443,12 +2974,15 @@ var generateSchemaData = (db, schema, relations, options) => {
2443
2974
  listFieldName,
2444
2975
  singleFieldName,
2445
2976
  aggregateFieldName,
2977
+ groupByFieldName,
2446
2978
  createArrayFieldName,
2447
2979
  createSingleFieldName,
2448
2980
  upsertArrayFieldName,
2449
2981
  upsertSingleFieldName,
2450
2982
  updateFieldName,
2451
- deleteFieldName
2983
+ updateSingleFieldName,
2984
+ deleteFieldName,
2985
+ deleteSingleFieldName
2452
2986
  } = computeResolverFieldNames(tableName, typeNameMapper, prefixes, suffixes);
2453
2987
  const selectArrGenerated = generateSelectArray(
2454
2988
  db,
@@ -2493,9 +3027,41 @@ var generateSchemaData = (db, schema, relations, options) => {
2493
3027
  updateInput,
2494
3028
  tableFilters,
2495
3029
  updateFieldName,
3030
+ false,
3031
+ features.requireWhere,
3032
+ filterCtx
3033
+ ) : void 0;
3034
+ const updateSingleGenerated = features.update ? generateUpdate(
3035
+ db,
3036
+ tableName,
3037
+ schema[tableName],
3038
+ updateInput,
3039
+ tableFilters,
3040
+ updateSingleFieldName,
3041
+ true,
3042
+ features.requireWhere,
3043
+ filterCtx
3044
+ ) : void 0;
3045
+ const deleteGenerated = features.delete ? generateDelete(
3046
+ db,
3047
+ tableName,
3048
+ schema[tableName],
3049
+ tableFilters,
3050
+ deleteFieldName,
3051
+ false,
3052
+ features.requireWhere,
3053
+ filterCtx
3054
+ ) : void 0;
3055
+ const deleteSingleGenerated = features.delete ? generateDelete(
3056
+ db,
3057
+ tableName,
3058
+ schema[tableName],
3059
+ tableFilters,
3060
+ deleteSingleFieldName,
3061
+ true,
3062
+ features.requireWhere,
2496
3063
  filterCtx
2497
3064
  ) : void 0;
2498
- const deleteGenerated = features.delete ? generateDelete(db, tableName, schema[tableName], tableFilters, deleteFieldName, filterCtx) : void 0;
2499
3065
  const aggregateType = features.aggregates ? generateAggregateTypes(schema[tableName], tableName, typeName, cacheCtx) : void 0;
2500
3066
  const aggregateGenerated = features.aggregates ? generateAggregate(
2501
3067
  db,
@@ -2506,6 +3072,20 @@ var generateSchemaData = (db, schema, relations, options) => {
2506
3072
  tableFilters,
2507
3073
  filterCtx
2508
3074
  ) : void 0;
3075
+ const groupByType = features.aggregates && features.groupBy ? generateGroupByType(schema[tableName], tableName, typeName, cacheCtx) : void 0;
3076
+ const groupByEnum = groupByType ? generateGroupByEnum(schema[tableName], tableName, typeName) : void 0;
3077
+ const havingInput = groupByEnum ? generateHavingInput(schema[tableName], tableName, typeName) : void 0;
3078
+ const groupByGenerated = groupByType && groupByEnum && havingInput ? generateGroupBy(
3079
+ db,
3080
+ tableName,
3081
+ schema[tableName],
3082
+ typeName,
3083
+ groupByFieldName,
3084
+ tableFilters,
3085
+ groupByEnum,
3086
+ havingInput,
3087
+ filterCtx
3088
+ ) : void 0;
2509
3089
  queries[selectArrGenerated.name] = {
2510
3090
  type: selectArrOutput,
2511
3091
  args: selectArrGenerated.args,
@@ -2525,13 +3105,23 @@ var generateSchemaData = (db, schema, relations, options) => {
2525
3105
  ...complexity ? { extensions: { complexity: aggregateFieldComplexity(complexity) } } : {}
2526
3106
  };
2527
3107
  }
3108
+ if (groupByGenerated && groupByType) {
3109
+ queries[groupByGenerated.name] = {
3110
+ type: new import_graphql6.GraphQLNonNull(new import_graphql6.GraphQLList(new import_graphql6.GraphQLNonNull(groupByType))),
3111
+ args: groupByGenerated.args,
3112
+ resolve: groupByGenerated.resolver,
3113
+ ...complexity ? { extensions: { complexity: aggregateFieldComplexity(complexity) } } : {}
3114
+ };
3115
+ }
2528
3116
  for (const generated of [
2529
3117
  insertArrGenerated,
2530
3118
  insertSingleGenerated,
2531
3119
  upsertArrGenerated,
2532
3120
  upsertSingleGenerated,
2533
3121
  updateGenerated,
2534
- deleteGenerated
3122
+ updateSingleGenerated,
3123
+ deleteGenerated,
3124
+ deleteSingleGenerated
2535
3125
  ]) {
2536
3126
  if (generated) {
2537
3127
  mutations[generated.name] = {
@@ -2556,6 +3146,10 @@ var generateSchemaData = (db, schema, relations, options) => {
2556
3146
  if (aggregateType) {
2557
3147
  outputs[aggregateType.name] = aggregateType;
2558
3148
  }
3149
+ if (groupByType && havingInput) {
3150
+ outputs[groupByType.name] = groupByType;
3151
+ inputs[havingInput.name] = havingInput;
3152
+ }
2559
3153
  }
2560
3154
  const fieldResolvers = {};
2561
3155
  for (const [tableName, tableRelations] of Object.entries(namedRelations)) {
@@ -2643,7 +3237,7 @@ var generateSelectArray2 = (db, tableName, tables, relationMap, orderArgs, filte
2643
3237
  }
2644
3238
  if (orderBy) {
2645
3239
  q = q.orderBy(
2646
- ...extractOrderBy(table, orderBy),
3240
+ ...extractOrderBy(table, orderBy, relationFilterCtx(filterCtx, tableName)),
2647
3241
  ...distinctKeys ? primaryKeyOrderExprs(table, pkNames) : []
2648
3242
  );
2649
3243
  } else if ((distinctKeys || offset != null || limit != null) && pkNames.length) {
@@ -2704,7 +3298,7 @@ var generateSelectSingle2 = (db, tableName, tables, relationMap, orderArgs, filt
2704
3298
  q = q.where(extractFilters(table, tableName, where, relationFilterCtx(filterCtx, tableName)));
2705
3299
  }
2706
3300
  if (orderBy) {
2707
- q = q.orderBy(...extractOrderBy(table, orderBy));
3301
+ q = q.orderBy(...extractOrderBy(table, orderBy, relationFilterCtx(filterCtx, tableName)));
2708
3302
  } else if (pkNames.length) {
2709
3303
  q = q.orderBy(...primaryKeyOrderExprs(table, pkNames));
2710
3304
  }
@@ -2864,13 +3458,13 @@ var generateUpsert2 = (db, tableName, table, tables, relationMap, baseType, onCo
2864
3458
  args: queryArgs
2865
3459
  };
2866
3460
  };
2867
- var generateUpdate2 = (db, tableName, table, tables, relationMap, setArgs, filterArgs, fieldName, typeName, typeNameMapper, filterCtx) => {
3461
+ var generateUpdate2 = (db, tableName, table, tables, relationMap, setArgs, filterArgs, fieldName, typeName, single, requireWhere, typeNameMapper, filterCtx) => {
2868
3462
  const queryArgs = {
2869
3463
  set: {
2870
3464
  type: new import_graphql7.GraphQLNonNull(setArgs)
2871
3465
  },
2872
3466
  where: {
2873
- type: filterArgs
3467
+ type: single || requireWhere ? new import_graphql7.GraphQLNonNull(filterArgs) : filterArgs
2874
3468
  }
2875
3469
  };
2876
3470
  const pkNames = pgPrimaryKeyPropNames(table);
@@ -2896,16 +3490,26 @@ var generateUpdate2 = (db, tableName, table, tables, relationMap, setArgs, filte
2896
3490
  if (!Object.keys(input).length) {
2897
3491
  throw new import_graphql7.GraphQLError("Unable to update with no values specified!");
2898
3492
  }
3493
+ const relationCtx = relationFilterCtx(filterCtx, tableName);
3494
+ const filters = single || requireWhere ? extractRequiredFilters(table, tableName, where, fieldName, relationCtx) : where ? extractFilters(table, tableName, where, relationCtx) : void 0;
2899
3495
  const executor = resolveExecutor(db, context);
3496
+ if (single) {
3497
+ await assertSingleMatch(executor, table, filters, fieldName);
3498
+ }
2900
3499
  let query = executor.update(table).set(input);
2901
- if (where) {
2902
- const filters = extractFilters(table, tableName, where, relationFilterCtx(filterCtx, tableName));
3500
+ if (filters) {
2903
3501
  query = query.where(filters);
2904
3502
  }
2905
3503
  query = query.returning(columns);
2906
3504
  const result = await query;
3505
+ if (single && result.length > 1) {
3506
+ throw new import_graphql7.GraphQLError(`${fieldName}: 'where' matched more than one row!`);
3507
+ }
3508
+ if (single && !result[0]) {
3509
+ return void 0;
3510
+ }
2907
3511
  const enriched = hasRelations ? await eagerLoadMutationRelations(executor, tableName, result, pkNames, withParams) : result;
2908
- return remapToGraphQLArrayOutput(enriched, tableName, table, relationMap);
3512
+ return single ? remapToGraphQLSingleOutput(enriched[0], tableName, table, relationMap) : remapToGraphQLArrayOutput(enriched, tableName, table, relationMap);
2909
3513
  } catch (e) {
2910
3514
  throw toGraphQLError(e);
2911
3515
  }
@@ -2913,10 +3517,10 @@ var generateUpdate2 = (db, tableName, table, tables, relationMap, setArgs, filte
2913
3517
  args: queryArgs
2914
3518
  };
2915
3519
  };
2916
- var generateDelete2 = (db, tableName, table, filterArgs, fieldName, typeName, filterCtx, selectionCtx) => {
3520
+ var generateDelete2 = (db, tableName, table, filterArgs, fieldName, typeName, single, requireWhere, filterCtx, selectionCtx) => {
2917
3521
  const queryArgs = {
2918
3522
  where: {
2919
- type: filterArgs
3523
+ type: single || requireWhere ? new import_graphql7.GraphQLNonNull(filterArgs) : filterArgs
2920
3524
  }
2921
3525
  };
2922
3526
  return {
@@ -2932,14 +3536,24 @@ var generateDelete2 = (db, tableName, table, filterArgs, fieldName, typeName, fi
2932
3536
  table,
2933
3537
  selectionCtx
2934
3538
  );
3539
+ const relationCtx = relationFilterCtx(filterCtx, tableName);
3540
+ const filters = single || requireWhere ? extractRequiredFilters(table, tableName, where, fieldName, relationCtx) : where ? extractFilters(table, tableName, where, relationCtx) : void 0;
2935
3541
  const executor = resolveExecutor(db, context);
3542
+ if (single) {
3543
+ await assertSingleMatch(executor, table, filters, fieldName);
3544
+ }
2936
3545
  let query = executor.delete(table);
2937
- if (where) {
2938
- const filters = extractFilters(table, tableName, where, relationFilterCtx(filterCtx, tableName));
3546
+ if (filters) {
2939
3547
  query = query.where(filters);
2940
3548
  }
2941
3549
  query = query.returning(columns);
2942
3550
  const result = await query;
3551
+ if (single && result.length > 1) {
3552
+ throw new import_graphql7.GraphQLError(`${fieldName}: 'where' matched more than one row!`);
3553
+ }
3554
+ if (single) {
3555
+ return result[0] ? remapToGraphQLSingleOutput(result[0], tableName, table) : void 0;
3556
+ }
2943
3557
  return remapToGraphQLArrayOutput(result, tableName, table);
2944
3558
  } catch (e) {
2945
3559
  throw toGraphQLError(e);
@@ -3015,12 +3629,15 @@ function generateSchemaData2(db, schema, relations, options) {
3015
3629
  listFieldName,
3016
3630
  singleFieldName,
3017
3631
  aggregateFieldName,
3632
+ groupByFieldName,
3018
3633
  createArrayFieldName,
3019
3634
  createSingleFieldName,
3020
3635
  upsertArrayFieldName,
3021
3636
  upsertSingleFieldName,
3022
3637
  updateFieldName,
3023
- deleteFieldName
3638
+ updateSingleFieldName,
3639
+ deleteFieldName,
3640
+ deleteSingleFieldName
3024
3641
  } = computeResolverFieldNames(tableName, typeNameMapper, prefixes, suffixes);
3025
3642
  const selectArrGenerated = generateSelectArray2(
3026
3643
  db,
@@ -3119,6 +3736,23 @@ function generateSchemaData2(db, schema, relations, options) {
3119
3736
  tableFilters,
3120
3737
  updateFieldName,
3121
3738
  typeName,
3739
+ false,
3740
+ features.requireWhere,
3741
+ typeNameMapper,
3742
+ filterCtx
3743
+ ) : void 0;
3744
+ const updateSingleGenerated = features.update ? generateUpdate2(
3745
+ db,
3746
+ tableName,
3747
+ schema[tableName],
3748
+ tables,
3749
+ eagerRelations,
3750
+ updateInput,
3751
+ tableFilters,
3752
+ updateSingleFieldName,
3753
+ typeName,
3754
+ true,
3755
+ features.requireWhere,
3122
3756
  typeNameMapper,
3123
3757
  filterCtx
3124
3758
  ) : void 0;
@@ -3129,6 +3763,20 @@ function generateSchemaData2(db, schema, relations, options) {
3129
3763
  tableFilters,
3130
3764
  deleteFieldName,
3131
3765
  typeName,
3766
+ false,
3767
+ features.requireWhere,
3768
+ filterCtx,
3769
+ { tableName, relationMap: namedRelations, tables }
3770
+ ) : void 0;
3771
+ const deleteSingleGenerated = features.delete ? generateDelete2(
3772
+ db,
3773
+ tableName,
3774
+ schema[tableName],
3775
+ tableFilters,
3776
+ deleteSingleFieldName,
3777
+ typeName,
3778
+ true,
3779
+ features.requireWhere,
3132
3780
  filterCtx,
3133
3781
  { tableName, relationMap: namedRelations, tables }
3134
3782
  ) : void 0;
@@ -3142,6 +3790,20 @@ function generateSchemaData2(db, schema, relations, options) {
3142
3790
  tableFilters,
3143
3791
  filterCtx
3144
3792
  ) : void 0;
3793
+ const groupByType = features.aggregates && features.groupBy ? generateGroupByType(schema[tableName], tableName, typeName, cacheCtx) : void 0;
3794
+ const groupByEnum = groupByType ? generateGroupByEnum(schema[tableName], tableName, typeName) : void 0;
3795
+ const havingInput = groupByEnum ? generateHavingInput(schema[tableName], tableName, typeName) : void 0;
3796
+ const groupByGenerated = groupByType && groupByEnum && havingInput ? generateGroupBy(
3797
+ db,
3798
+ tableName,
3799
+ schema[tableName],
3800
+ typeName,
3801
+ groupByFieldName,
3802
+ tableFilters,
3803
+ groupByEnum,
3804
+ havingInput,
3805
+ filterCtx
3806
+ ) : void 0;
3145
3807
  queries[selectArrGenerated.name] = {
3146
3808
  type: selectArrOutput,
3147
3809
  args: selectArrGenerated.args,
@@ -3161,6 +3823,14 @@ function generateSchemaData2(db, schema, relations, options) {
3161
3823
  ...complexity ? { extensions: { complexity: aggregateFieldComplexity(complexity) } } : {}
3162
3824
  };
3163
3825
  }
3826
+ if (groupByGenerated && groupByType) {
3827
+ queries[groupByGenerated.name] = {
3828
+ type: new import_graphql7.GraphQLNonNull(new import_graphql7.GraphQLList(new import_graphql7.GraphQLNonNull(groupByType))),
3829
+ args: groupByGenerated.args,
3830
+ resolve: groupByGenerated.resolver,
3831
+ ...complexity ? { extensions: { complexity: aggregateFieldComplexity(complexity) } } : {}
3832
+ };
3833
+ }
3164
3834
  if (insertArrGenerated) {
3165
3835
  mutations[insertArrGenerated.name] = {
3166
3836
  type: arrTableItemOutput,
@@ -3196,6 +3866,13 @@ function generateSchemaData2(db, schema, relations, options) {
3196
3866
  resolve: updateGenerated.resolver
3197
3867
  };
3198
3868
  }
3869
+ if (updateSingleGenerated) {
3870
+ mutations[updateSingleGenerated.name] = {
3871
+ type: singleTableItemOutput,
3872
+ args: updateSingleGenerated.args,
3873
+ resolve: updateSingleGenerated.resolver
3874
+ };
3875
+ }
3199
3876
  if (deleteGenerated) {
3200
3877
  mutations[deleteGenerated.name] = {
3201
3878
  type: arrTableItemOutput,
@@ -3203,6 +3880,13 @@ function generateSchemaData2(db, schema, relations, options) {
3203
3880
  resolve: deleteGenerated.resolver
3204
3881
  };
3205
3882
  }
3883
+ if (deleteSingleGenerated) {
3884
+ mutations[deleteSingleGenerated.name] = {
3885
+ type: singleTableItemOutput,
3886
+ args: deleteSingleGenerated.args,
3887
+ resolve: deleteSingleGenerated.resolver
3888
+ };
3889
+ }
3206
3890
  const activeInputs = [
3207
3891
  // The insert input types the upsert mutations too, so either feature keeps it.
3208
3892
  ...features.insert || onConflictInput ? [insertInput] : [],
@@ -3219,6 +3903,10 @@ function generateSchemaData2(db, schema, relations, options) {
3219
3903
  if (aggregateType) {
3220
3904
  outputs[aggregateType.name] = aggregateType;
3221
3905
  }
3906
+ if (groupByType && havingInput) {
3907
+ outputs[groupByType.name] = groupByType;
3908
+ inputs[havingInput.name] = havingInput;
3909
+ }
3222
3910
  }
3223
3911
  const fieldResolvers = {};
3224
3912
  for (const [tableName, tableRelations] of Object.entries(namedRelations)) {
@@ -3465,13 +4153,13 @@ var generateUpsert3 = (db, tableName, table, tables, relationMap, baseType, onCo
3465
4153
  args: queryArgs
3466
4154
  };
3467
4155
  };
3468
- var generateUpdate3 = (db, tableName, table, tables, relationMap, setArgs, filterArgs, fieldName, typeName, typeNameMapper, filterCtx) => {
4156
+ var generateUpdate3 = (db, tableName, table, tables, relationMap, setArgs, filterArgs, fieldName, typeName, single, requireWhere, typeNameMapper, filterCtx) => {
3469
4157
  const queryArgs = {
3470
4158
  set: {
3471
4159
  type: new import_graphql8.GraphQLNonNull(setArgs)
3472
4160
  },
3473
4161
  where: {
3474
- type: filterArgs
4162
+ type: single || requireWhere ? new import_graphql8.GraphQLNonNull(filterArgs) : filterArgs
3475
4163
  }
3476
4164
  };
3477
4165
  const pkNames = sqlitePrimaryKeyPropNames(table);
@@ -3497,16 +4185,26 @@ var generateUpdate3 = (db, tableName, table, tables, relationMap, setArgs, filte
3497
4185
  if (!Object.keys(input).length) {
3498
4186
  throw new import_graphql8.GraphQLError("Unable to update with no values specified!");
3499
4187
  }
4188
+ const relationCtx = relationFilterCtx(filterCtx, tableName);
4189
+ const filters = single || requireWhere ? extractRequiredFilters(table, tableName, where, fieldName, relationCtx) : where ? extractFilters(table, tableName, where, relationCtx) : void 0;
3500
4190
  const executor = resolveExecutor(db, context);
4191
+ if (single) {
4192
+ await assertSingleMatch(executor, table, filters, fieldName);
4193
+ }
3501
4194
  let query = executor.update(table).set(input);
3502
- if (where) {
3503
- const filters = extractFilters(table, tableName, where, relationFilterCtx(filterCtx, tableName));
4195
+ if (filters) {
3504
4196
  query = query.where(filters);
3505
4197
  }
3506
4198
  query = query.returning(columns);
3507
4199
  const result = await query;
4200
+ if (single && result.length > 1) {
4201
+ throw new import_graphql8.GraphQLError(`${fieldName}: 'where' matched more than one row!`);
4202
+ }
4203
+ if (single && !result[0]) {
4204
+ return void 0;
4205
+ }
3508
4206
  const enriched = hasRelations ? await eagerLoadMutationRelations(executor, tableName, result, pkNames, withParams) : result;
3509
- return remapToGraphQLArrayOutput(enriched, tableName, table, relationMap);
4207
+ return single ? remapToGraphQLSingleOutput(enriched[0], tableName, table, relationMap) : remapToGraphQLArrayOutput(enriched, tableName, table, relationMap);
3510
4208
  } catch (e) {
3511
4209
  throw toGraphQLError(e);
3512
4210
  }
@@ -3514,10 +4212,10 @@ var generateUpdate3 = (db, tableName, table, tables, relationMap, setArgs, filte
3514
4212
  args: queryArgs
3515
4213
  };
3516
4214
  };
3517
- var generateDelete3 = (db, tableName, table, filterArgs, fieldName, typeName, filterCtx, selectionCtx) => {
4215
+ var generateDelete3 = (db, tableName, table, filterArgs, fieldName, typeName, single, requireWhere, filterCtx, selectionCtx) => {
3518
4216
  const queryArgs = {
3519
4217
  where: {
3520
- type: filterArgs
4218
+ type: single || requireWhere ? new import_graphql8.GraphQLNonNull(filterArgs) : filterArgs
3521
4219
  }
3522
4220
  };
3523
4221
  return {
@@ -3533,14 +4231,24 @@ var generateDelete3 = (db, tableName, table, filterArgs, fieldName, typeName, fi
3533
4231
  table,
3534
4232
  selectionCtx
3535
4233
  );
4234
+ const relationCtx = relationFilterCtx(filterCtx, tableName);
4235
+ const filters = single || requireWhere ? extractRequiredFilters(table, tableName, where, fieldName, relationCtx) : where ? extractFilters(table, tableName, where, relationCtx) : void 0;
3536
4236
  const executor = resolveExecutor(db, context);
4237
+ if (single) {
4238
+ await assertSingleMatch(executor, table, filters, fieldName);
4239
+ }
3537
4240
  let query = executor.delete(table);
3538
- if (where) {
3539
- const filters = extractFilters(table, tableName, where, relationFilterCtx(filterCtx, tableName));
4241
+ if (filters) {
3540
4242
  query = query.where(filters);
3541
4243
  }
3542
4244
  query = query.returning(columns);
3543
4245
  const result = await query;
4246
+ if (single && result.length > 1) {
4247
+ throw new import_graphql8.GraphQLError(`${fieldName}: 'where' matched more than one row!`);
4248
+ }
4249
+ if (single) {
4250
+ return result[0] ? remapToGraphQLSingleOutput(result[0], tableName, table) : void 0;
4251
+ }
3544
4252
  return remapToGraphQLArrayOutput(result, tableName, table);
3545
4253
  } catch (e) {
3546
4254
  throw toGraphQLError(e);
@@ -3617,12 +4325,15 @@ var generateSchemaData3 = (db, schema, relations, options) => {
3617
4325
  listFieldName,
3618
4326
  singleFieldName,
3619
4327
  aggregateFieldName,
4328
+ groupByFieldName,
3620
4329
  createArrayFieldName,
3621
4330
  createSingleFieldName,
3622
4331
  upsertArrayFieldName,
3623
4332
  upsertSingleFieldName,
3624
4333
  updateFieldName,
3625
- deleteFieldName
4334
+ updateSingleFieldName,
4335
+ deleteFieldName,
4336
+ deleteSingleFieldName
3626
4337
  } = computeResolverFieldNames(tableName, typeNameMapper, prefixes, suffixes);
3627
4338
  const selectArrGenerated = generateSelectArray3(
3628
4339
  db,
@@ -3721,6 +4432,23 @@ var generateSchemaData3 = (db, schema, relations, options) => {
3721
4432
  tableFilters,
3722
4433
  updateFieldName,
3723
4434
  typeName,
4435
+ false,
4436
+ features.requireWhere,
4437
+ typeNameMapper,
4438
+ filterCtx
4439
+ ) : void 0;
4440
+ const updateSingleGenerated = features.update ? generateUpdate3(
4441
+ db,
4442
+ tableName,
4443
+ schema[tableName],
4444
+ tables,
4445
+ eagerRelations,
4446
+ updateInput,
4447
+ tableFilters,
4448
+ updateSingleFieldName,
4449
+ typeName,
4450
+ true,
4451
+ features.requireWhere,
3724
4452
  typeNameMapper,
3725
4453
  filterCtx
3726
4454
  ) : void 0;
@@ -3731,6 +4459,20 @@ var generateSchemaData3 = (db, schema, relations, options) => {
3731
4459
  tableFilters,
3732
4460
  deleteFieldName,
3733
4461
  typeName,
4462
+ false,
4463
+ features.requireWhere,
4464
+ filterCtx,
4465
+ { tableName, relationMap: namedRelations, tables }
4466
+ ) : void 0;
4467
+ const deleteSingleGenerated = features.delete ? generateDelete3(
4468
+ db,
4469
+ tableName,
4470
+ schema[tableName],
4471
+ tableFilters,
4472
+ deleteSingleFieldName,
4473
+ typeName,
4474
+ true,
4475
+ features.requireWhere,
3734
4476
  filterCtx,
3735
4477
  { tableName, relationMap: namedRelations, tables }
3736
4478
  ) : void 0;
@@ -3744,6 +4486,20 @@ var generateSchemaData3 = (db, schema, relations, options) => {
3744
4486
  tableFilters,
3745
4487
  filterCtx
3746
4488
  ) : void 0;
4489
+ const groupByType = features.aggregates && features.groupBy ? generateGroupByType(schema[tableName], tableName, typeName, cacheCtx) : void 0;
4490
+ const groupByEnum = groupByType ? generateGroupByEnum(schema[tableName], tableName, typeName) : void 0;
4491
+ const havingInput = groupByEnum ? generateHavingInput(schema[tableName], tableName, typeName) : void 0;
4492
+ const groupByGenerated = groupByType && groupByEnum && havingInput ? generateGroupBy(
4493
+ db,
4494
+ tableName,
4495
+ schema[tableName],
4496
+ typeName,
4497
+ groupByFieldName,
4498
+ tableFilters,
4499
+ groupByEnum,
4500
+ havingInput,
4501
+ filterCtx
4502
+ ) : void 0;
3747
4503
  queries[selectArrGenerated.name] = {
3748
4504
  type: selectArrOutput,
3749
4505
  args: selectArrGenerated.args,
@@ -3763,6 +4519,14 @@ var generateSchemaData3 = (db, schema, relations, options) => {
3763
4519
  ...complexity ? { extensions: { complexity: aggregateFieldComplexity(complexity) } } : {}
3764
4520
  };
3765
4521
  }
4522
+ if (groupByGenerated && groupByType) {
4523
+ queries[groupByGenerated.name] = {
4524
+ type: new import_graphql8.GraphQLNonNull(new import_graphql8.GraphQLList(new import_graphql8.GraphQLNonNull(groupByType))),
4525
+ args: groupByGenerated.args,
4526
+ resolve: groupByGenerated.resolver,
4527
+ ...complexity ? { extensions: { complexity: aggregateFieldComplexity(complexity) } } : {}
4528
+ };
4529
+ }
3766
4530
  if (insertArrGenerated) {
3767
4531
  mutations[insertArrGenerated.name] = {
3768
4532
  type: arrTableItemOutput,
@@ -3798,6 +4562,13 @@ var generateSchemaData3 = (db, schema, relations, options) => {
3798
4562
  resolve: updateGenerated.resolver
3799
4563
  };
3800
4564
  }
4565
+ if (updateSingleGenerated) {
4566
+ mutations[updateSingleGenerated.name] = {
4567
+ type: singleTableItemOutput,
4568
+ args: updateSingleGenerated.args,
4569
+ resolve: updateSingleGenerated.resolver
4570
+ };
4571
+ }
3801
4572
  if (deleteGenerated) {
3802
4573
  mutations[deleteGenerated.name] = {
3803
4574
  type: arrTableItemOutput,
@@ -3805,6 +4576,13 @@ var generateSchemaData3 = (db, schema, relations, options) => {
3805
4576
  resolve: deleteGenerated.resolver
3806
4577
  };
3807
4578
  }
4579
+ if (deleteSingleGenerated) {
4580
+ mutations[deleteSingleGenerated.name] = {
4581
+ type: singleTableItemOutput,
4582
+ args: deleteSingleGenerated.args,
4583
+ resolve: deleteSingleGenerated.resolver
4584
+ };
4585
+ }
3808
4586
  const activeInputs = [
3809
4587
  // The insert input types the upsert mutations too, so either feature keeps it.
3810
4588
  ...features.insert || onConflictInput ? [insertInput] : [],
@@ -3821,6 +4599,10 @@ var generateSchemaData3 = (db, schema, relations, options) => {
3821
4599
  if (aggregateType) {
3822
4600
  outputs[aggregateType.name] = aggregateType;
3823
4601
  }
4602
+ if (groupByType && havingInput) {
4603
+ outputs[groupByType.name] = groupByType;
4604
+ inputs[havingInput.name] = havingInput;
4605
+ }
3824
4606
  }
3825
4607
  const fieldResolvers = {};
3826
4608
  for (const [tableName, tableRelations] of Object.entries(namedRelations)) {
@@ -3863,12 +4645,14 @@ var buildSchema = (db, config) => {
3863
4645
  const typeNameMapper = config?.typeNameMapper;
3864
4646
  const features = {
3865
4647
  aggregates: config?.features?.aggregates ?? true,
4648
+ groupBy: config?.features?.groupBy ?? true,
3866
4649
  relationAggregates: config?.features?.relationAggregates ?? true,
3867
4650
  distinct: config?.features?.distinct ?? true,
3868
4651
  insert: config?.features?.insert ?? true,
3869
4652
  update: config?.features?.update ?? true,
3870
4653
  delete: config?.features?.delete ?? true,
3871
- upsert: config?.features?.upsert ?? false
4654
+ upsert: config?.features?.upsert ?? false,
4655
+ requireWhere: config?.features?.requireWhere ?? false
3872
4656
  };
3873
4657
  const complexityConfig = config?.complexity ?? true;
3874
4658
  const complexity = complexityConfig === false ? void 0 : {
@@ -3942,6 +4726,7 @@ var buildSchema = (db, config) => {
3942
4726
  GraphQLBigIntString,
3943
4727
  GraphQLDate,
3944
4728
  GraphQLDateTime,
4729
+ GraphQLDecimalString,
3945
4730
  GraphQLJSON,
3946
4731
  GraphQLUUID,
3947
4732
  buildSchema,