@graphql-hive/gateway-runtime 2.7.9 → 2.8.0-alpha-bb7cb0747a2f268a4a03e2f7f7f2ccd1364ded49

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/CHANGELOG.md CHANGED
@@ -1,5 +1,30 @@
1
1
  # @graphql-hive/gateway-runtime
2
2
 
3
+ ## 2.8.0-alpha-bb7cb0747a2f268a4a03e2f7f7f2ccd1364ded49
4
+ ### Minor Changes
5
+
6
+
7
+
8
+ - [#2133](https://github.com/graphql-hive/gateway/pull/2133) [`18ca078`](https://github.com/graphql-hive/gateway/commit/18ca078a3b34afd9420e37f758713b597fa05352) Thanks [@copilot-swe-agent](https://github.com/apps/copilot-swe-agent)! - Allow `maxCost` in `DemandControlPluginOptions` to accept a function for dynamic cost limiting.
9
+
10
+ Previously, `maxCost` only accepted a static `number`. It now also accepts a synchronous or
11
+ asynchronous function `(payload: DemandControlMaxCostPayload) => MaybePromise<number>`, where
12
+ `payload` contains:
13
+
14
+ - `operationCost` – the estimated cost of the current subgraph operation
15
+ - `totalCost` – the accumulated cost for the whole request context so far
16
+ - `subgraphName` – the name of the subgraph being executed
17
+ - `executionRequest` – the full execution request object
18
+
19
+ This lets you implement per-user rate limits, per-subgraph budgets, or any other context-aware
20
+ cost policy. The `DemandControlMaxCostPayload` interface is exported from the package for use
21
+ when typing your `maxCost` function.
22
+
23
+ ### Patch Changes
24
+
25
+ - Updated dependencies []:
26
+ - @graphql-mesh/hmac-upstream-signature@2.0.10
27
+
3
28
  ## 2.7.9
4
29
  ### Patch Changes
5
30
 
package/dist/index.cjs CHANGED
@@ -1226,9 +1226,11 @@ function useDemandControl({
1226
1226
  fieldCost,
1227
1227
  typeCost
1228
1228
  });
1229
+ const maxCostFn = maxCost == null ? null : typeof maxCost === "function" ? maxCost : () => maxCost;
1229
1230
  const costByContextMap = /* @__PURE__ */ new WeakMap();
1231
+ const resolvedMaxCostByContextMap = /* @__PURE__ */ new WeakMap();
1230
1232
  return {
1231
- onSubgraphExecute({ subgraph, executionRequest, log }) {
1233
+ onSubgraphExecute({ subgraph, subgraphName, executionRequest, log }) {
1232
1234
  if (!subgraph) {
1233
1235
  return;
1234
1236
  }
@@ -1249,16 +1251,34 @@ function useDemandControl({
1249
1251
  },
1250
1252
  "[useDemandControl]"
1251
1253
  );
1252
- if (maxCost != null && costByContext > maxCost) {
1253
- throw utils.createGraphQLError(
1254
- `Operation estimated cost ${costByContext} exceeded configured maximum ${maxCost}`,
1255
- {
1256
- extensions: {
1257
- code: "COST_ESTIMATED_TOO_EXPENSIVE",
1258
- cost: {
1259
- estimated: costByContext,
1260
- max: maxCost
1261
- }
1254
+ if (maxCostFn != null) {
1255
+ return promiseHelpers.handleMaybePromise(
1256
+ () => maxCostFn({
1257
+ operationCost,
1258
+ totalCost: costByContext,
1259
+ subgraphName,
1260
+ executionRequest
1261
+ }),
1262
+ (resolvedMaxCost) => {
1263
+ if (executionRequest.context) {
1264
+ resolvedMaxCostByContextMap.set(
1265
+ executionRequest.context,
1266
+ resolvedMaxCost
1267
+ );
1268
+ }
1269
+ if (costByContext > resolvedMaxCost) {
1270
+ throw utils.createGraphQLError(
1271
+ `Operation estimated cost ${costByContext} exceeded configured maximum ${resolvedMaxCost}`,
1272
+ {
1273
+ extensions: {
1274
+ code: "COST_ESTIMATED_TOO_EXPENSIVE",
1275
+ cost: {
1276
+ estimated: costByContext,
1277
+ max: resolvedMaxCost
1278
+ }
1279
+ }
1280
+ }
1281
+ );
1262
1282
  }
1263
1283
  }
1264
1284
  );
@@ -1267,6 +1287,7 @@ function useDemandControl({
1267
1287
  onExecutionResult({ result, setResult, context }) {
1268
1288
  if (includeExtensionMetadata) {
1269
1289
  const costByContext = costByContextMap.get(context) || 0;
1290
+ const resolvedMaxCost = resolvedMaxCostByContextMap.get(context);
1270
1291
  if (utils.isAsyncIterable(result)) {
1271
1292
  setResult(
1272
1293
  utils.mapAsyncIterator(result, (value) => ({
@@ -1275,7 +1296,7 @@ function useDemandControl({
1275
1296
  ...value.extensions || {},
1276
1297
  cost: {
1277
1298
  estimated: costByContext,
1278
- max: maxCost
1299
+ ...resolvedMaxCost != null ? { max: resolvedMaxCost } : {}
1279
1300
  }
1280
1301
  }
1281
1302
  }))
@@ -1287,7 +1308,7 @@ function useDemandControl({
1287
1308
  ...result?.extensions || {},
1288
1309
  cost: {
1289
1310
  estimated: costByContext,
1290
- max: maxCost
1311
+ ...resolvedMaxCost != null ? { max: resolvedMaxCost } : {}
1291
1312
  }
1292
1313
  }
1293
1314
  });
package/dist/index.d.cts CHANGED
@@ -18,7 +18,7 @@ import { ResponseCacheConfig } from '@graphql-mesh/plugin-response-cache';
18
18
  import { MeshFetch, KeyValueCache, MeshFetchRequestInit, Logger as Logger$1 } from '@graphql-mesh/types';
19
19
  import { FetchInstrumentation } from '@graphql-mesh/utils';
20
20
  import { HTTPExecutorOptions } from '@graphql-tools/executor-http';
21
- import { ExecutionRequest, MaybeAsyncIterable, ExecutionResult, MaybePromise, ValidationRule, TypeSource, IResolvers, Executor } from '@graphql-tools/utils';
21
+ import { ExecutionRequest, MaybePromise, MaybeAsyncIterable, ExecutionResult, ValidationRule, TypeSource, IResolvers, Executor } from '@graphql-tools/utils';
22
22
  import { CSRFPreventionPluginOptions } from '@graphql-yoga/plugin-csrf-prevention';
23
23
  import { UsePersistedOperationsOptions } from '@graphql-yoga/plugin-persisted-operations';
24
24
  import { GraphQLResolveInfo } from 'graphql/type';
@@ -37,13 +37,34 @@ interface UseContentEncodingOpts {
37
37
 
38
38
  type AgentFactory<TContext> = (payload: OnFetchHookPayload<Partial<TContext> & GatewayContext & Record<string, any>>) => Agent | Agent$1 | false | undefined;
39
39
 
40
+ interface DemandControlMaxCostPayload {
41
+ /**
42
+ * The estimated cost of the current subgraph operation.
43
+ */
44
+ operationCost: number;
45
+ /**
46
+ * The total estimated cost accumulated for the whole request context so far.
47
+ */
48
+ totalCost: number;
49
+ /**
50
+ * The name of the subgraph being executed.
51
+ */
52
+ subgraphName: string;
53
+ /**
54
+ * The execution request being processed.
55
+ */
56
+ executionRequest: ExecutionRequest<any, any>;
57
+ }
40
58
  interface DemandControlPluginOptions {
41
59
  /**
42
- * The maximum cost of an accepted operation. An operation with a higher cost than this is rejected.
43
- * If not provided, no maximum cost is enforced.
44
- * @default Infinity
60
+ * The maximum cost of an accepted operation. An operation with a higher cost than this is rejected.
61
+ * Can be a static number or a function (sync or async) that receives cost details and returns the
62
+ * maximum cost allowed. Use a function to implement dynamic cost limits based on the context,
63
+ * subgraph, or estimated cost.
64
+ * If not provided, no maximum cost is enforced.
65
+ * @default Infinity
45
66
  */
46
- maxCost?: number;
67
+ maxCost?: number | ((payload: DemandControlMaxCostPayload) => MaybePromise<number>);
47
68
  /**
48
69
  * The assumed maximum size of a list for fields that return lists.
49
70
  * @default 0
@@ -922,4 +943,4 @@ declare function getProxyExecutor<TContext extends Record<string, any>>({ config
922
943
 
923
944
  declare function getGraphQLWSOptions<TContext extends Record<string, any>, E>(yoga: YogaServerInstance<Record<string, any>, TContext>, onContext: (ctx: Context<ConnectionInitMessage['payload'], E>) => MaybePromise<Record<string, unknown>>): ServerOptions<ConnectionInitMessage['payload'], E>;
924
945
 
925
- export { type GatewayConfig, type GatewayConfigBase, type GatewayConfigContext, type GatewayConfigProxy, type GatewayConfigSchemaBase, type GatewayConfigSubgraph, type GatewayConfigSupergraph, type GatewayContext, type GatewayGraphOSManagedFederationOptions, type GatewayGraphOSOptions, type GatewayGraphOSReportingOptions, type GatewayHiveCDNOptions, type GatewayHivePersistedDocumentsOptions, type GatewayHiveReportingOptions, type GatewayPlugin, type GatewayRuntime, type Instrumentation, type OnCacheDeleteHook, type OnCacheDeleteHookEventPayload, type OnCacheDeleteHookResult, type OnCacheErrorHook, type OnCacheErrorHookPayload, type OnCacheGetHook, type OnCacheGetHookEventPayload, type OnCacheGetHookResult, type OnCacheHitHook, type OnCacheHitHookEventPayload, type OnCacheMissHook, type OnCacheSetHook, type OnCacheSetHookEventPayload, type OnCacheSetHookResult, type OnFetchHook, type OnFetchHookDone, type OnFetchHookDonePayload, type OnFetchHookPayload, type ProgressiveOverrideHandler, type PropagateHeadersOpts, type StaticFilesOpts, type UnifiedGraphConfig, createGatewayRuntime, createLoggerFromLogging, getGraphQLWSOptions, getProxyExecutor, getRetryInfo, isRetryExecutionRequest, useCustomFetch, usePropagateHeaders, useStaticFiles, useUpstreamRetry, useUpstreamTimeout };
946
+ export { type DemandControlMaxCostPayload, type GatewayConfig, type GatewayConfigBase, type GatewayConfigContext, type GatewayConfigProxy, type GatewayConfigSchemaBase, type GatewayConfigSubgraph, type GatewayConfigSupergraph, type GatewayContext, type GatewayGraphOSManagedFederationOptions, type GatewayGraphOSOptions, type GatewayGraphOSReportingOptions, type GatewayHiveCDNOptions, type GatewayHivePersistedDocumentsOptions, type GatewayHiveReportingOptions, type GatewayPlugin, type GatewayRuntime, type Instrumentation, type OnCacheDeleteHook, type OnCacheDeleteHookEventPayload, type OnCacheDeleteHookResult, type OnCacheErrorHook, type OnCacheErrorHookPayload, type OnCacheGetHook, type OnCacheGetHookEventPayload, type OnCacheGetHookResult, type OnCacheHitHook, type OnCacheHitHookEventPayload, type OnCacheMissHook, type OnCacheSetHook, type OnCacheSetHookEventPayload, type OnCacheSetHookResult, type OnFetchHook, type OnFetchHookDone, type OnFetchHookDonePayload, type OnFetchHookPayload, type ProgressiveOverrideHandler, type PropagateHeadersOpts, type StaticFilesOpts, type UnifiedGraphConfig, createGatewayRuntime, createLoggerFromLogging, getGraphQLWSOptions, getProxyExecutor, getRetryInfo, isRetryExecutionRequest, useCustomFetch, usePropagateHeaders, useStaticFiles, useUpstreamRetry, useUpstreamTimeout };
package/dist/index.d.ts CHANGED
@@ -18,7 +18,7 @@ import { ResponseCacheConfig } from '@graphql-mesh/plugin-response-cache';
18
18
  import { MeshFetch, KeyValueCache, MeshFetchRequestInit, Logger as Logger$1 } from '@graphql-mesh/types';
19
19
  import { FetchInstrumentation } from '@graphql-mesh/utils';
20
20
  import { HTTPExecutorOptions } from '@graphql-tools/executor-http';
21
- import { ExecutionRequest, MaybeAsyncIterable, ExecutionResult, MaybePromise, ValidationRule, TypeSource, IResolvers, Executor } from '@graphql-tools/utils';
21
+ import { ExecutionRequest, MaybePromise, MaybeAsyncIterable, ExecutionResult, ValidationRule, TypeSource, IResolvers, Executor } from '@graphql-tools/utils';
22
22
  import { CSRFPreventionPluginOptions } from '@graphql-yoga/plugin-csrf-prevention';
23
23
  import { UsePersistedOperationsOptions } from '@graphql-yoga/plugin-persisted-operations';
24
24
  import { GraphQLResolveInfo } from 'graphql/type';
@@ -37,13 +37,34 @@ interface UseContentEncodingOpts {
37
37
 
38
38
  type AgentFactory<TContext> = (payload: OnFetchHookPayload<Partial<TContext> & GatewayContext & Record<string, any>>) => Agent | Agent$1 | false | undefined;
39
39
 
40
+ interface DemandControlMaxCostPayload {
41
+ /**
42
+ * The estimated cost of the current subgraph operation.
43
+ */
44
+ operationCost: number;
45
+ /**
46
+ * The total estimated cost accumulated for the whole request context so far.
47
+ */
48
+ totalCost: number;
49
+ /**
50
+ * The name of the subgraph being executed.
51
+ */
52
+ subgraphName: string;
53
+ /**
54
+ * The execution request being processed.
55
+ */
56
+ executionRequest: ExecutionRequest<any, any>;
57
+ }
40
58
  interface DemandControlPluginOptions {
41
59
  /**
42
- * The maximum cost of an accepted operation. An operation with a higher cost than this is rejected.
43
- * If not provided, no maximum cost is enforced.
44
- * @default Infinity
60
+ * The maximum cost of an accepted operation. An operation with a higher cost than this is rejected.
61
+ * Can be a static number or a function (sync or async) that receives cost details and returns the
62
+ * maximum cost allowed. Use a function to implement dynamic cost limits based on the context,
63
+ * subgraph, or estimated cost.
64
+ * If not provided, no maximum cost is enforced.
65
+ * @default Infinity
45
66
  */
46
- maxCost?: number;
67
+ maxCost?: number | ((payload: DemandControlMaxCostPayload) => MaybePromise<number>);
47
68
  /**
48
69
  * The assumed maximum size of a list for fields that return lists.
49
70
  * @default 0
@@ -922,4 +943,4 @@ declare function getProxyExecutor<TContext extends Record<string, any>>({ config
922
943
 
923
944
  declare function getGraphQLWSOptions<TContext extends Record<string, any>, E>(yoga: YogaServerInstance<Record<string, any>, TContext>, onContext: (ctx: Context<ConnectionInitMessage['payload'], E>) => MaybePromise<Record<string, unknown>>): ServerOptions<ConnectionInitMessage['payload'], E>;
924
945
 
925
- export { type GatewayConfig, type GatewayConfigBase, type GatewayConfigContext, type GatewayConfigProxy, type GatewayConfigSchemaBase, type GatewayConfigSubgraph, type GatewayConfigSupergraph, type GatewayContext, type GatewayGraphOSManagedFederationOptions, type GatewayGraphOSOptions, type GatewayGraphOSReportingOptions, type GatewayHiveCDNOptions, type GatewayHivePersistedDocumentsOptions, type GatewayHiveReportingOptions, type GatewayPlugin, type GatewayRuntime, type Instrumentation, type OnCacheDeleteHook, type OnCacheDeleteHookEventPayload, type OnCacheDeleteHookResult, type OnCacheErrorHook, type OnCacheErrorHookPayload, type OnCacheGetHook, type OnCacheGetHookEventPayload, type OnCacheGetHookResult, type OnCacheHitHook, type OnCacheHitHookEventPayload, type OnCacheMissHook, type OnCacheSetHook, type OnCacheSetHookEventPayload, type OnCacheSetHookResult, type OnFetchHook, type OnFetchHookDone, type OnFetchHookDonePayload, type OnFetchHookPayload, type ProgressiveOverrideHandler, type PropagateHeadersOpts, type StaticFilesOpts, type UnifiedGraphConfig, createGatewayRuntime, createLoggerFromLogging, getGraphQLWSOptions, getProxyExecutor, getRetryInfo, isRetryExecutionRequest, useCustomFetch, usePropagateHeaders, useStaticFiles, useUpstreamRetry, useUpstreamTimeout };
946
+ export { type DemandControlMaxCostPayload, type GatewayConfig, type GatewayConfigBase, type GatewayConfigContext, type GatewayConfigProxy, type GatewayConfigSchemaBase, type GatewayConfigSubgraph, type GatewayConfigSupergraph, type GatewayContext, type GatewayGraphOSManagedFederationOptions, type GatewayGraphOSOptions, type GatewayGraphOSReportingOptions, type GatewayHiveCDNOptions, type GatewayHivePersistedDocumentsOptions, type GatewayHiveReportingOptions, type GatewayPlugin, type GatewayRuntime, type Instrumentation, type OnCacheDeleteHook, type OnCacheDeleteHookEventPayload, type OnCacheDeleteHookResult, type OnCacheErrorHook, type OnCacheErrorHookPayload, type OnCacheGetHook, type OnCacheGetHookEventPayload, type OnCacheGetHookResult, type OnCacheHitHook, type OnCacheHitHookEventPayload, type OnCacheMissHook, type OnCacheSetHook, type OnCacheSetHookEventPayload, type OnCacheSetHookResult, type OnFetchHook, type OnFetchHookDone, type OnFetchHookDonePayload, type OnFetchHookPayload, type ProgressiveOverrideHandler, type PropagateHeadersOpts, type StaticFilesOpts, type UnifiedGraphConfig, createGatewayRuntime, createLoggerFromLogging, getGraphQLWSOptions, getProxyExecutor, getRetryInfo, isRetryExecutionRequest, useCustomFetch, usePropagateHeaders, useStaticFiles, useUpstreamRetry, useUpstreamTimeout };
package/dist/index.js CHANGED
@@ -1225,9 +1225,11 @@ function useDemandControl({
1225
1225
  fieldCost,
1226
1226
  typeCost
1227
1227
  });
1228
+ const maxCostFn = maxCost == null ? null : typeof maxCost === "function" ? maxCost : () => maxCost;
1228
1229
  const costByContextMap = /* @__PURE__ */ new WeakMap();
1230
+ const resolvedMaxCostByContextMap = /* @__PURE__ */ new WeakMap();
1229
1231
  return {
1230
- onSubgraphExecute({ subgraph, executionRequest, log }) {
1232
+ onSubgraphExecute({ subgraph, subgraphName, executionRequest, log }) {
1231
1233
  if (!subgraph) {
1232
1234
  return;
1233
1235
  }
@@ -1248,16 +1250,34 @@ function useDemandControl({
1248
1250
  },
1249
1251
  "[useDemandControl]"
1250
1252
  );
1251
- if (maxCost != null && costByContext > maxCost) {
1252
- throw createGraphQLError(
1253
- `Operation estimated cost ${costByContext} exceeded configured maximum ${maxCost}`,
1254
- {
1255
- extensions: {
1256
- code: "COST_ESTIMATED_TOO_EXPENSIVE",
1257
- cost: {
1258
- estimated: costByContext,
1259
- max: maxCost
1260
- }
1253
+ if (maxCostFn != null) {
1254
+ return handleMaybePromise(
1255
+ () => maxCostFn({
1256
+ operationCost,
1257
+ totalCost: costByContext,
1258
+ subgraphName,
1259
+ executionRequest
1260
+ }),
1261
+ (resolvedMaxCost) => {
1262
+ if (executionRequest.context) {
1263
+ resolvedMaxCostByContextMap.set(
1264
+ executionRequest.context,
1265
+ resolvedMaxCost
1266
+ );
1267
+ }
1268
+ if (costByContext > resolvedMaxCost) {
1269
+ throw createGraphQLError(
1270
+ `Operation estimated cost ${costByContext} exceeded configured maximum ${resolvedMaxCost}`,
1271
+ {
1272
+ extensions: {
1273
+ code: "COST_ESTIMATED_TOO_EXPENSIVE",
1274
+ cost: {
1275
+ estimated: costByContext,
1276
+ max: resolvedMaxCost
1277
+ }
1278
+ }
1279
+ }
1280
+ );
1261
1281
  }
1262
1282
  }
1263
1283
  );
@@ -1266,6 +1286,7 @@ function useDemandControl({
1266
1286
  onExecutionResult({ result, setResult, context }) {
1267
1287
  if (includeExtensionMetadata) {
1268
1288
  const costByContext = costByContextMap.get(context) || 0;
1289
+ const resolvedMaxCost = resolvedMaxCostByContextMap.get(context);
1269
1290
  if (isAsyncIterable(result)) {
1270
1291
  setResult(
1271
1292
  mapAsyncIterator(result, (value) => ({
@@ -1274,7 +1295,7 @@ function useDemandControl({
1274
1295
  ...value.extensions || {},
1275
1296
  cost: {
1276
1297
  estimated: costByContext,
1277
- max: maxCost
1298
+ ...resolvedMaxCost != null ? { max: resolvedMaxCost } : {}
1278
1299
  }
1279
1300
  }
1280
1301
  }))
@@ -1286,7 +1307,7 @@ function useDemandControl({
1286
1307
  ...result?.extensions || {},
1287
1308
  cost: {
1288
1309
  estimated: costByContext,
1289
- max: maxCost
1310
+ ...resolvedMaxCost != null ? { max: resolvedMaxCost } : {}
1290
1311
  }
1291
1312
  }
1292
1313
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@graphql-hive/gateway-runtime",
3
- "version": "2.7.9",
3
+ "version": "2.8.0-alpha-bb7cb0747a2f268a4a03e2f7f7f2ccd1364ded49",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",