@vantreeseba/drizzle-graphql 4.0.0 → 4.1.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/README.md CHANGED
@@ -397,6 +397,52 @@ The build-wide `conflictDoNothing` option is deprecated in favour of this: it ap
397
397
  `create*` mutation with no way for a request to opt out. `onConflict: { action: NOTHING }` is
398
398
  the per-request replacement.
399
399
 
400
+ ## Query cost
401
+
402
+ Generated fields carry a `complexity` hint in their GraphQL extensions, ready for
403
+ [`graphql-query-complexity`](https://github.com/slicknode/graphql-query-complexity)'s
404
+ `fieldExtensionsEstimator`. The generator knows which fields are paginated and which are
405
+ aggregates, so the cost tracks the rows a query can actually pull rather than the number of
406
+ fields it mentions:
407
+
408
+ | Field | Cost |
409
+ | --- | --- |
410
+ | List query, to-many relation | `(limit ?? defaultListSize) * childComplexity` |
411
+ | Aggregate query, `<relation>Aggregate` | `aggregateCost + childComplexity` |
412
+ | Everything else | no hint — your estimator's default applies |
413
+
414
+ So `users(limit: 20) { id posts(limit: 5) { id } }` costs `20 * (1 + 5 * 1)` = 120, while the
415
+ same query without limits costs `10 * (1 + 10 * 1)` = 110.
416
+
417
+ The hints do nothing until you install a complexity rule, so they are generated by default:
418
+
419
+ ```ts
420
+ import { createComplexityRule, fieldExtensionsEstimator, simpleEstimator } from 'graphql-query-complexity';
421
+
422
+ const rule = createComplexityRule({
423
+ maximumComplexity: 1000,
424
+ estimators: [fieldExtensionsEstimator(), simpleEstimator({ defaultComplexity: 1 })],
425
+ });
426
+ ```
427
+
428
+ Tune the two assumptions, or turn the hints off entirely:
429
+
430
+ ```ts
431
+ buildSchema(db, {
432
+ complexity: { defaultListSize: 25, aggregateCost: 50 },
433
+ });
434
+
435
+ buildSchema(db, { complexity: false });
436
+ ```
437
+
438
+ ### Depth
439
+
440
+ Cost is not a depth bound. A cyclic relation graph (`user -> posts -> author -> posts -> …`)
441
+ lets a client nest as deep as it likes, and a deep query over cheap fields can stay under any
442
+ complexity ceiling. Put a depth limit in front of a publicly exposed schema as well — e.g.
443
+ [`graphql-depth-limit`](https://github.com/stems/graphql-depth-limit) — or set
444
+ `relationsDepthLimit: 0` to generate no relation fields at all.
445
+
400
446
  ## Error handling
401
447
 
402
448
  Database drivers put a lot into an error message. Drizzle rethrows them with the full SQL
package/dist/README.md CHANGED
@@ -397,6 +397,52 @@ The build-wide `conflictDoNothing` option is deprecated in favour of this: it ap
397
397
  `create*` mutation with no way for a request to opt out. `onConflict: { action: NOTHING }` is
398
398
  the per-request replacement.
399
399
 
400
+ ## Query cost
401
+
402
+ Generated fields carry a `complexity` hint in their GraphQL extensions, ready for
403
+ [`graphql-query-complexity`](https://github.com/slicknode/graphql-query-complexity)'s
404
+ `fieldExtensionsEstimator`. The generator knows which fields are paginated and which are
405
+ aggregates, so the cost tracks the rows a query can actually pull rather than the number of
406
+ fields it mentions:
407
+
408
+ | Field | Cost |
409
+ | --- | --- |
410
+ | List query, to-many relation | `(limit ?? defaultListSize) * childComplexity` |
411
+ | Aggregate query, `<relation>Aggregate` | `aggregateCost + childComplexity` |
412
+ | Everything else | no hint — your estimator's default applies |
413
+
414
+ So `users(limit: 20) { id posts(limit: 5) { id } }` costs `20 * (1 + 5 * 1)` = 120, while the
415
+ same query without limits costs `10 * (1 + 10 * 1)` = 110.
416
+
417
+ The hints do nothing until you install a complexity rule, so they are generated by default:
418
+
419
+ ```ts
420
+ import { createComplexityRule, fieldExtensionsEstimator, simpleEstimator } from 'graphql-query-complexity';
421
+
422
+ const rule = createComplexityRule({
423
+ maximumComplexity: 1000,
424
+ estimators: [fieldExtensionsEstimator(), simpleEstimator({ defaultComplexity: 1 })],
425
+ });
426
+ ```
427
+
428
+ Tune the two assumptions, or turn the hints off entirely:
429
+
430
+ ```ts
431
+ buildSchema(db, {
432
+ complexity: { defaultListSize: 25, aggregateCost: 50 },
433
+ });
434
+
435
+ buildSchema(db, { complexity: false });
436
+ ```
437
+
438
+ ### Depth
439
+
440
+ Cost is not a depth bound. A cyclic relation graph (`user -> posts -> author -> posts -> …`)
441
+ lets a client nest as deep as it likes, and a deep query over cheap fields can stay under any
442
+ complexity ceiling. Put a depth limit in front of a publicly exposed schema as well — e.g.
443
+ [`graphql-depth-limit`](https://github.com/stems/graphql-depth-limit) — or set
444
+ `relationsDepthLimit: 0` to generate no relation fields at all.
445
+
400
446
  ## Error handling
401
447
 
402
448
  Database drivers put a lot into an error message. Drizzle rethrows them with the full SQL
package/dist/index.cjs CHANGED
@@ -650,6 +650,12 @@ var createRelationResolverFactory = (db, tables, filterCtx) => ({ tableName, rel
650
650
  return loader.load(localValue);
651
651
  };
652
652
  };
653
+ var listFieldComplexity = (options) => ({ args, childComplexity }) => {
654
+ const limit = args["limit"];
655
+ const rows = typeof limit === "number" && limit > 0 ? limit : options.defaultListSize;
656
+ return rows * Math.max(childComplexity, 1);
657
+ };
658
+ var aggregateFieldComplexity = (options) => ({ childComplexity }) => options.aggregateCost + childComplexity;
653
659
  var AGGREGATE_FIELD_SUFFIX = "Aggregate";
654
660
  var relationAggregateJoinColumns = (tree, table, selectionCtx) => {
655
661
  const relations = selectionCtx?.relationMap[selectionCtx.tableName];
@@ -1071,7 +1077,8 @@ var generateSelectFields = (tables, tableName, relationMap, fromTableName, fromR
1071
1077
  offset: { type: import_graphql4.GraphQLInt },
1072
1078
  limit: { type: import_graphql4.GraphQLInt }
1073
1079
  },
1074
- resolve
1080
+ resolve,
1081
+ ...cacheCtx.complexity ? { extensions: { complexity: listFieldComplexity(cacheCtx.complexity) } } : {}
1075
1082
  }
1076
1083
  ]);
1077
1084
  const aggregateFieldName = `${relationName}Aggregate`;
@@ -1089,7 +1096,8 @@ var generateSelectFields = (tables, tableName, relationMap, fromTableName, fromR
1089
1096
  args: {
1090
1097
  where: { type: relSelectData.filters }
1091
1098
  },
1092
- resolve: relationAggregate.resolve
1099
+ resolve: relationAggregate.resolve,
1100
+ ...cacheCtx.complexity ? { extensions: { complexity: aggregateFieldComplexity(cacheCtx.complexity) } } : {}
1093
1101
  }
1094
1102
  ]);
1095
1103
  }
@@ -2366,7 +2374,7 @@ var generateDelete = (db, tableName, table, filterArgs, fieldName, filterCtx) =>
2366
2374
  };
2367
2375
  var mysqlPrimaryKeyPropNames = (table) => getPrimaryKeyPropNamesFromConfig(table, import_mysql_core2.getTableConfig);
2368
2376
  var generateSchemaData = (db, schema, relations, options) => {
2369
- const { relationsDepthLimit, prefixes, suffixes, typeNameMapper, shouldEagerLoad, features } = options;
2377
+ const { relationsDepthLimit, prefixes, suffixes, typeNameMapper, shouldEagerLoad, features, complexity } = options;
2370
2378
  const rawSchema = schema;
2371
2379
  const schemaEntries = Object.entries(rawSchema);
2372
2380
  const tableEntries = schemaEntries.filter(([_key, value]) => (0, import_drizzle_orm5.is)(value, import_mysql_core2.MySqlTable));
@@ -2390,7 +2398,8 @@ var generateSchemaData = (db, schema, relations, options) => {
2390
2398
  orderTypeCache: /* @__PURE__ */ new WeakMap(),
2391
2399
  filterTypeCache: /* @__PURE__ */ new WeakMap(),
2392
2400
  listRelationFilterCache: /* @__PURE__ */ new Map(),
2393
- aggregateTypeCache: /* @__PURE__ */ new Map()
2401
+ aggregateTypeCache: /* @__PURE__ */ new Map(),
2402
+ complexity
2394
2403
  };
2395
2404
  const relationAggregateFactory = features.relationAggregates ? createRelationAggregateFactory(db, tables, cacheCtx, typeNameMapper, filterCtx) : void 0;
2396
2405
  const queries = {};
@@ -2500,7 +2509,8 @@ var generateSchemaData = (db, schema, relations, options) => {
2500
2509
  queries[selectArrGenerated.name] = {
2501
2510
  type: selectArrOutput,
2502
2511
  args: selectArrGenerated.args,
2503
- resolve: selectArrGenerated.resolver
2512
+ resolve: selectArrGenerated.resolver,
2513
+ ...complexity ? { extensions: { complexity: listFieldComplexity(complexity) } } : {}
2504
2514
  };
2505
2515
  queries[selectSingleGenerated.name] = {
2506
2516
  type: selectSingleOutput,
@@ -2511,7 +2521,8 @@ var generateSchemaData = (db, schema, relations, options) => {
2511
2521
  queries[aggregateGenerated.name] = {
2512
2522
  type: new import_graphql6.GraphQLNonNull(aggregateType),
2513
2523
  args: aggregateGenerated.args,
2514
- resolve: aggregateGenerated.resolver
2524
+ resolve: aggregateGenerated.resolver,
2525
+ ...complexity ? { extensions: { complexity: aggregateFieldComplexity(complexity) } } : {}
2515
2526
  };
2516
2527
  }
2517
2528
  for (const generated of [
@@ -2938,7 +2949,16 @@ var generateDelete2 = (db, tableName, table, filterArgs, fieldName, typeName, fi
2938
2949
  };
2939
2950
  };
2940
2951
  function generateSchemaData2(db, schema, relations, options) {
2941
- const { relationsDepthLimit, prefixes, suffixes, conflictDoNothing, typeNameMapper, shouldEagerLoad, features } = options;
2952
+ const {
2953
+ relationsDepthLimit,
2954
+ prefixes,
2955
+ suffixes,
2956
+ conflictDoNothing,
2957
+ typeNameMapper,
2958
+ shouldEagerLoad,
2959
+ features,
2960
+ complexity
2961
+ } = options;
2942
2962
  const schemaEntries = Object.entries(schema);
2943
2963
  const tableEntries = schemaEntries.filter(([_key, value]) => (0, import_drizzle_orm6.is)(value, import_pg_core2.PgTable));
2944
2964
  const tables = Object.fromEntries(tableEntries);
@@ -2961,7 +2981,8 @@ function generateSchemaData2(db, schema, relations, options) {
2961
2981
  orderTypeCache: /* @__PURE__ */ new WeakMap(),
2962
2982
  filterTypeCache: /* @__PURE__ */ new WeakMap(),
2963
2983
  listRelationFilterCache: /* @__PURE__ */ new Map(),
2964
- aggregateTypeCache: /* @__PURE__ */ new Map()
2984
+ aggregateTypeCache: /* @__PURE__ */ new Map(),
2985
+ complexity
2965
2986
  };
2966
2987
  const relationAggregateFactory = features.relationAggregates ? createRelationAggregateFactory(db, tables, cacheCtx, typeNameMapper, filterCtx) : void 0;
2967
2988
  const queries = {};
@@ -3124,7 +3145,8 @@ function generateSchemaData2(db, schema, relations, options) {
3124
3145
  queries[selectArrGenerated.name] = {
3125
3146
  type: selectArrOutput,
3126
3147
  args: selectArrGenerated.args,
3127
- resolve: selectArrGenerated.resolver
3148
+ resolve: selectArrGenerated.resolver,
3149
+ ...complexity ? { extensions: { complexity: listFieldComplexity(complexity) } } : {}
3128
3150
  };
3129
3151
  queries[selectSingleGenerated.name] = {
3130
3152
  type: selectSingleOutput,
@@ -3135,7 +3157,8 @@ function generateSchemaData2(db, schema, relations, options) {
3135
3157
  queries[aggregateGenerated.name] = {
3136
3158
  type: new import_graphql7.GraphQLNonNull(aggregateType),
3137
3159
  args: aggregateGenerated.args,
3138
- resolve: aggregateGenerated.resolver
3160
+ resolve: aggregateGenerated.resolver,
3161
+ ...complexity ? { extensions: { complexity: aggregateFieldComplexity(complexity) } } : {}
3139
3162
  };
3140
3163
  }
3141
3164
  if (insertArrGenerated) {
@@ -3527,7 +3550,16 @@ var generateDelete3 = (db, tableName, table, filterArgs, fieldName, typeName, fi
3527
3550
  };
3528
3551
  };
3529
3552
  var generateSchemaData3 = (db, schema, relations, options) => {
3530
- const { relationsDepthLimit, prefixes, suffixes, conflictDoNothing, typeNameMapper, shouldEagerLoad, features } = options;
3553
+ const {
3554
+ relationsDepthLimit,
3555
+ prefixes,
3556
+ suffixes,
3557
+ conflictDoNothing,
3558
+ typeNameMapper,
3559
+ shouldEagerLoad,
3560
+ features,
3561
+ complexity
3562
+ } = options;
3531
3563
  const rawSchema = schema;
3532
3564
  const schemaEntries = Object.entries(rawSchema);
3533
3565
  const tableEntries = schemaEntries.filter(([_key, value]) => (0, import_drizzle_orm7.is)(value, import_sqlite_core2.SQLiteTable));
@@ -3551,7 +3583,8 @@ var generateSchemaData3 = (db, schema, relations, options) => {
3551
3583
  orderTypeCache: /* @__PURE__ */ new WeakMap(),
3552
3584
  filterTypeCache: /* @__PURE__ */ new WeakMap(),
3553
3585
  listRelationFilterCache: /* @__PURE__ */ new Map(),
3554
- aggregateTypeCache: /* @__PURE__ */ new Map()
3586
+ aggregateTypeCache: /* @__PURE__ */ new Map(),
3587
+ complexity
3555
3588
  };
3556
3589
  const relationAggregateFactory = features.relationAggregates ? createRelationAggregateFactory(db, tables, cacheCtx, typeNameMapper, filterCtx) : void 0;
3557
3590
  const queries = {};
@@ -3714,7 +3747,8 @@ var generateSchemaData3 = (db, schema, relations, options) => {
3714
3747
  queries[selectArrGenerated.name] = {
3715
3748
  type: selectArrOutput,
3716
3749
  args: selectArrGenerated.args,
3717
- resolve: selectArrGenerated.resolver
3750
+ resolve: selectArrGenerated.resolver,
3751
+ ...complexity ? { extensions: { complexity: listFieldComplexity(complexity) } } : {}
3718
3752
  };
3719
3753
  queries[selectSingleGenerated.name] = {
3720
3754
  type: selectSingleOutput,
@@ -3725,7 +3759,8 @@ var generateSchemaData3 = (db, schema, relations, options) => {
3725
3759
  queries[aggregateGenerated.name] = {
3726
3760
  type: new import_graphql8.GraphQLNonNull(aggregateType),
3727
3761
  args: aggregateGenerated.args,
3728
- resolve: aggregateGenerated.resolver
3762
+ resolve: aggregateGenerated.resolver,
3763
+ ...complexity ? { extensions: { complexity: aggregateFieldComplexity(complexity) } } : {}
3729
3764
  };
3730
3765
  }
3731
3766
  if (insertArrGenerated) {
@@ -3835,6 +3870,11 @@ var buildSchema = (db, config) => {
3835
3870
  delete: config?.features?.delete ?? true,
3836
3871
  upsert: config?.features?.upsert ?? false
3837
3872
  };
3873
+ const complexityConfig = config?.complexity ?? true;
3874
+ const complexity = complexityConfig === false ? void 0 : {
3875
+ defaultListSize: (complexityConfig === true ? void 0 : complexityConfig.defaultListSize) ?? 10,
3876
+ aggregateCost: (complexityConfig === true ? void 0 : complexityConfig.aggregateCost) ?? 10
3877
+ };
3838
3878
  const eagerOpt = config?.eagerLoadRelations;
3839
3879
  const shouldEagerLoad = eagerOpt === void 0 || eagerOpt === true ? () => true : eagerOpt === false ? () => false : eagerOpt;
3840
3880
  if (!typeNameMapper && suffixes.list === suffixes.single) {
@@ -3861,7 +3901,8 @@ var buildSchema = (db, config) => {
3861
3901
  conflictDoNothing: config?.conflictDoNothing ?? false,
3862
3902
  typeNameMapper,
3863
3903
  shouldEagerLoad,
3864
- features
3904
+ features,
3905
+ complexity
3865
3906
  };
3866
3907
  let generatorOutput;
3867
3908
  if ((0, import_drizzle_orm8.is)(db, import_mysql_core3.MySqlDatabase)) {