diff --git a/.changeset/@graphql-hive_gateway-runtime-809-dependencies.md b/.changeset/@graphql-hive_gateway-runtime-809-dependencies.md new file mode 100644 index 000000000..18edcc67e --- /dev/null +++ b/.changeset/@graphql-hive_gateway-runtime-809-dependencies.md @@ -0,0 +1,8 @@ +--- +'@graphql-hive/gateway-runtime': patch +--- + +dependencies updates: + +- Added dependency [`@graphql-hive/yoga@^0.41.0` ↗︎](https://www.npmjs.com/package/@graphql-hive/yoga/v/0.41.0) (to `dependencies`) +- Removed dependency [`@graphql-mesh/plugin-hive@^0.104.0` ↗︎](https://www.npmjs.com/package/@graphql-mesh/plugin-hive/v/0.104.0) (from `dependencies`) diff --git a/.changeset/plenty-llamas-push.md b/.changeset/plenty-llamas-push.md new file mode 100644 index 000000000..a6273f4f9 --- /dev/null +++ b/.changeset/plenty-llamas-push.md @@ -0,0 +1,7 @@ +--- +'@graphql-hive/gateway': minor +--- + +Introduce `target` as a new Hive reporting option + +Deprecate the `--hive-registry-token` CLI option in favour of `--hive-usage-target` and `--hive-usage-access-token` options. [Read more on Hive's product update page.](https://the-guild.dev/graphql/hive/product-updates/2025-03-10-new-access-tokens) \ No newline at end of file diff --git a/.changeset/wet-kiwis-peel.md b/.changeset/wet-kiwis-peel.md new file mode 100644 index 000000000..65421aae9 --- /dev/null +++ b/.changeset/wet-kiwis-peel.md @@ -0,0 +1,7 @@ +--- +'@graphql-hive/gateway-runtime': minor +--- + +Introduce `target` as a new Hive reporting option + +[Read more on Hive's product update page.](https://the-guild.dev/graphql/hive/product-updates/2025-03-10-new-access-tokens) diff --git a/packages/gateway/src/cli.ts b/packages/gateway/src/cli.ts index d0afedde0..dc0f3598e 100644 --- a/packages/gateway/src/cli.ts +++ b/packages/gateway/src/cli.ts @@ -72,9 +72,17 @@ export interface GatewayCLISupergraphConfig } export interface GatewayCLIHiveReportingOptions - extends Omit { + extends Omit { /** - * Hive registry token for usage metrics reporting. + * The target to which the usage data should be reported to. + * + * @default process.env.HIVE_USAGE_TARGET + */ + target?: GatewayHiveReportingOptions['target']; + /** + * Hive registry access token for usage metrics reporting. + * + * @default process.env.HIVE_USAGE_ACCESS_TOKEN || process.env.HIVE_REGISTRY_TOKEN */ token?: GatewayHiveReportingOptions['token']; } @@ -307,9 +315,21 @@ let cli = new Command() .addOption( new Option( '--hive-registry-token ', - 'Hive registry token for usage metrics reporting', + '[DEPRECATED: please use "--hive-usage-target" and "--hive-usage-access-token"] Hive registry token for usage metrics reporting', ).env('HIVE_REGISTRY_TOKEN'), ) + .addOption( + new Option( + '--hive-usage-target ', + 'Hive registry target to which the usage data should be reported to. requires the "--hive-usage-access-token " option', + ).env('HIVE_USAGE_TARGET'), + ) + .addOption( + new Option( + '--hive-usage-access-token ', + 'Hive registry access token for usage metrics reporting. requires the "--hive-usage-target " option', + ).env('HIVE_USAGE_ACCESS_TOKEN'), + ) .option( '--hive-persisted-documents-endpoint ', '[EXPERIMENTAL] Hive CDN endpoint for fetching the persisted documents. requires the "--hive-persisted-documents-token " option', diff --git a/packages/gateway/src/commands/handleReportingConfig.ts b/packages/gateway/src/commands/handleReportingConfig.ts new file mode 100644 index 000000000..8e7405ad6 --- /dev/null +++ b/packages/gateway/src/commands/handleReportingConfig.ts @@ -0,0 +1,100 @@ +import { + CLIContext, + GatewayConfig, + GatewayGraphOSReportingOptions, + GatewayHiveReportingOptions, +} from '..'; + +export interface ReportingCLIOptions { + hiveRegistryToken: string | undefined; + hiveUsageTarget: string | undefined; + hiveUsageAccessToken: string | undefined; + apolloGraphRef: string | undefined; + apolloKey: string | undefined; +} + +export function handleReportingConfig( + ctx: CLIContext, + loadedConfig: Partial>>, + cliOpts: ReportingCLIOptions, +): GatewayHiveReportingOptions | GatewayGraphOSReportingOptions | null { + const confOpts: Partial = { + ...(loadedConfig.reporting?.type === 'hive' + ? { + hiveRegistryToken: loadedConfig.reporting.token, + hiveUsageTarget: loadedConfig.reporting.target, + hiveUsageAccessToken: loadedConfig.reporting.token, + } + : {}), + ...(loadedConfig.reporting?.type === 'graphos' + ? { + apolloGraphRef: loadedConfig.reporting.graphRef, + apolloKey: loadedConfig.reporting.apiKey, + } + : {}), + }; + const opts = { ...confOpts, ...cliOpts }; + + if (cliOpts.hiveRegistryToken && cliOpts.hiveUsageAccessToken) { + ctx.log.error( + `Cannot use "--hive-registry-token" with "--hive-usage-access-token". Please use "--hive-usage-target" and "--hive-usage-access-token" or the config instead.`, + ); + process.exit(1); + } + + if (cliOpts.hiveRegistryToken && opts.hiveUsageTarget) { + ctx.log.error( + `Cannot use "--hive-registry-token" with a target. Please use "--hive-usage-target" and "--hive-usage-access-token" or the config instead.`, + ); + process.exit(1); + } + + if (opts.hiveUsageTarget && !opts.hiveUsageAccessToken) { + ctx.log.error( + `Hive usage target needs an access token. Please provide it through the "--hive-usage-access-token " option or the config.`, + ); + process.exit(1); + } + + if (opts.hiveUsageAccessToken && !opts.hiveUsageTarget) { + ctx.log.error( + `Hive usage access token needs a target. Please provide it through the "--hive-usage-target " option or the config.`, + ); + process.exit(1); + } + + const hiveUsageAccessToken = + opts.hiveUsageAccessToken || opts.hiveRegistryToken; + if (hiveUsageAccessToken) { + // different logs w and w/o the target to disambiguate + if (opts.hiveUsageTarget) { + ctx.log.info(`Configuring Hive usage reporting`); + } else { + ctx.log.info(`Configuring Hive registry reporting`); + } + return { + ...loadedConfig.reporting, + type: 'hive', + token: hiveUsageAccessToken, + target: opts.hiveUsageTarget, + }; + } + + if (opts.apolloKey) { + ctx.log.info(`Configuring Apollo GraphOS registry reporting`); + if (!opts.apolloGraphRef) { + ctx.log.error( + `Apollo GraphOS requires a graph ref in the format @. Please provide a valid graph ref.`, + ); + process.exit(1); + } + return { + ...loadedConfig.reporting, + type: 'graphos', + apiKey: opts.apolloKey, + graphRef: opts.apolloGraphRef, + }; + } + + return null; +} diff --git a/packages/gateway/src/commands/proxy.ts b/packages/gateway/src/commands/proxy.ts index 2b08adf97..c93c44201 100644 --- a/packages/gateway/src/commands/proxy.ts +++ b/packages/gateway/src/commands/proxy.ts @@ -18,6 +18,7 @@ import { import { startServerForRuntime } from '../servers/startServerForRuntime'; import { handleFork } from './handleFork'; import { handleLoggingConfig } from './handleLoggingOption'; +import { handleReportingConfig } from './handleReportingConfig'; export const addCommand: AddCommand = (ctx, cli) => cli @@ -35,6 +36,8 @@ export const addCommand: AddCommand = (ctx, cli) => hiveCdnEndpoint, hiveCdnKey, hiveRegistryToken, + hiveUsageTarget, + hiveUsageAccessToken, maskedErrors, hivePersistedDocumentsEndpoint, hivePersistedDocumentsToken, @@ -95,6 +98,19 @@ export const addCommand: AddCommand = (ctx, cli) => process.exit(1); } + const registryConfig: Pick = {}; + const reporting = handleReportingConfig(ctx, loadedConfig, { + hiveRegistryToken, + hiveUsageTarget, + hiveUsageAccessToken, + // proxy can only do reporting to hive registry + apolloGraphRef: undefined, + apolloKey: undefined, + }); + if (reporting) { + registryConfig.reporting = reporting; + } + const pubsub = loadedConfig.pubsub || new PubSub(); const cwd = loadedConfig.cwd || process.cwd(); if (loadedConfig.logging != null) { @@ -128,15 +144,7 @@ export const addCommand: AddCommand = (ctx, cli) => ? loadedConfig.pollingInterval : undefined) || defaultOptions.pollingInterval, - ...(hiveRegistryToken - ? { - reporting: { - ...loadedConfig.reporting, - type: 'hive', - token: hiveRegistryToken, - }, - } - : {}), + ...registryConfig, proxy, schema, logging: ctx.log, diff --git a/packages/gateway/src/commands/subgraph.ts b/packages/gateway/src/commands/subgraph.ts index 08331c7c6..0de00ab80 100644 --- a/packages/gateway/src/commands/subgraph.ts +++ b/packages/gateway/src/commands/subgraph.ts @@ -22,6 +22,7 @@ import { import { startServerForRuntime } from '../servers/startServerForRuntime'; import { handleFork } from './handleFork'; import { handleLoggingConfig } from './handleLoggingOption'; +import { handleReportingConfig } from './handleReportingConfig'; export const addCommand: AddCommand = (ctx, cli) => cli @@ -37,6 +38,8 @@ export const addCommand: AddCommand = (ctx, cli) => const { maskedErrors, hiveRegistryToken, + hiveUsageTarget, + hiveUsageAccessToken, hivePersistedDocumentsEndpoint, hivePersistedDocumentsToken, ...opts @@ -55,6 +58,19 @@ export const addCommand: AddCommand = (ctx, cli) => subgraph = loadedConfig.subgraph!; // TODO: assertion wont be necessary when exactOptionalPropertyTypes } + const registryConfig: Pick = {}; + const reporting = handleReportingConfig(ctx, loadedConfig, { + hiveRegistryToken, + hiveUsageTarget, + hiveUsageAccessToken, + // subgraph can only do reporting to hive registry + apolloGraphRef: undefined, + apolloKey: undefined, + }); + if (reporting) { + registryConfig.reporting = reporting; + } + const pubsub = loadedConfig.pubsub || new PubSub(); const cwd = loadedConfig.cwd || process.cwd(); if (loadedConfig.logging != null) { @@ -88,15 +104,7 @@ export const addCommand: AddCommand = (ctx, cli) => ? loadedConfig.pollingInterval : undefined) || defaultOptions.pollingInterval, - ...(hiveRegistryToken - ? { - reporting: { - ...loadedConfig.reporting, - type: 'hive', - token: hiveRegistryToken, - }, - } - : {}), + ...registryConfig, subgraph, logging: loadedConfig.logging ?? ctx.log, productName: ctx.productName, diff --git a/packages/gateway/src/commands/supergraph.ts b/packages/gateway/src/commands/supergraph.ts index d5cfa9dd5..c454c0913 100644 --- a/packages/gateway/src/commands/supergraph.ts +++ b/packages/gateway/src/commands/supergraph.ts @@ -28,6 +28,7 @@ import { import { startServerForRuntime } from '../servers/startServerForRuntime'; import { handleFork } from './handleFork'; import { handleLoggingConfig } from './handleLoggingOption'; +import { handleReportingConfig } from './handleReportingConfig'; export const addCommand: AddCommand = (ctx, cli) => cli @@ -50,6 +51,8 @@ export const addCommand: AddCommand = (ctx, cli) => hiveCdnEndpoint, hiveCdnKey, hiveRegistryToken, + hiveUsageTarget, + hiveUsageAccessToken, maskedErrors, apolloGraphRef, apolloKey, @@ -151,32 +154,16 @@ export const addCommand: AddCommand = (ctx, cli) => ctx.log.info(`Using default supergraph location: ${supergraph}`); } - let registryConfig: Pick = {}; - - if (hiveRegistryToken) { - ctx.log.info(`Configuring Hive registry reporting`); - registryConfig = { - reporting: { - ...loadedConfig.reporting, - type: 'hive', - token: hiveRegistryToken, - }, - }; - } else if (apolloKey) { - ctx.log.info(`Configuring Apollo GraphOS registry reporting`); - if (!apolloGraphRef) { - ctx.log.error( - `Apollo GraphOS requires a graph ref in the format @. Please provide a valid graph ref.`, - ); - process.exit(1); - } - registryConfig = { - reporting: { - type: 'graphos', - apiKey: apolloKey, - graphRef: apolloGraphRef, - }, - }; + const registryConfig: Pick = {}; + const reporting = handleReportingConfig(ctx, loadedConfig, { + hiveRegistryToken, + hiveUsageTarget, + hiveUsageAccessToken, + apolloGraphRef, + apolloKey, + }); + if (reporting) { + registryConfig.reporting = reporting; } const pubsub = loadedConfig.pubsub || new PubSub(); diff --git a/packages/runtime/package.json b/packages/runtime/package.json index be224f563..be0f07dd9 100644 --- a/packages/runtime/package.json +++ b/packages/runtime/package.json @@ -49,10 +49,10 @@ "@envelop/generic-auth": "^9.0.0", "@graphql-hive/core": "^0.10.0", "@graphql-hive/logger-json": "workspace:^", + "@graphql-hive/yoga": "^0.41.0", "@graphql-mesh/cross-helpers": "^0.4.10", "@graphql-mesh/fusion-runtime": "workspace:^", "@graphql-mesh/hmac-upstream-signature": "workspace:^", - "@graphql-mesh/plugin-hive": "^0.104.0", "@graphql-mesh/plugin-response-cache": "^0.104.0", "@graphql-mesh/transport-common": "workspace:^", "@graphql-mesh/types": "^0.104.0", diff --git a/packages/runtime/src/createGatewayRuntime.ts b/packages/runtime/src/createGatewayRuntime.ts index ed0749359..a7ae50d03 100644 --- a/packages/runtime/src/createGatewayRuntime.ts +++ b/packages/runtime/src/createGatewayRuntime.ts @@ -21,7 +21,6 @@ import { UnifiedGraphManager, } from '@graphql-mesh/fusion-runtime'; import { useHmacUpstreamSignature } from '@graphql-mesh/hmac-upstream-signature'; -import useMeshHive from '@graphql-mesh/plugin-hive'; import useMeshResponseCache from '@graphql-mesh/plugin-response-cache'; import { TransportContext } from '@graphql-mesh/transport-common'; import type { @@ -96,6 +95,7 @@ import { useCustomAgent } from './plugins/useCustomAgent'; import { useDelegationPlanDebug } from './plugins/useDelegationPlanDebug'; import { useDemandControl } from './plugins/useDemandControl'; import { useFetchDebug } from './plugins/useFetchDebug'; +import useHiveConsole from './plugins/useHiveConsole'; import { usePropagateHeaders } from './plugins/usePropagateHeaders'; import { useRequestId } from './plugins/useRequestId'; import { useSubgraphExecuteDebug } from './plugins/useSubgraphExecuteDebug'; @@ -202,8 +202,9 @@ export function createGatewayRuntime< 'type' in config.persistedDocuments && config.persistedDocuments?.type === 'hive' ) { - persistedDocumentsPlugin = useMeshHive({ + persistedDocumentsPlugin = useHiveConsole({ ...configContext, + enabled: false, // disables only usage reporting logger: configContext.logger.child({ plugin: 'Hive Persisted Documents', }), diff --git a/packages/runtime/src/getReportingPlugin.ts b/packages/runtime/src/getReportingPlugin.ts index 2940fa53c..367a3bed8 100644 --- a/packages/runtime/src/getReportingPlugin.ts +++ b/packages/runtime/src/getReportingPlugin.ts @@ -1,5 +1,7 @@ -import useMeshHive from '@graphql-mesh/plugin-hive'; import { useApolloUsageReport } from '@graphql-yoga/plugin-apollo-usage-report'; +import useHiveConsole, { + HiveConsolePluginOptions, +} from './plugins/useHiveConsole'; import type { GatewayConfig, GatewayConfigContext, @@ -10,16 +12,28 @@ export function getReportingPlugin>( config: GatewayConfig, configContext: GatewayConfigContext, ): { - name?: string; + name?: 'Hive' | 'GraphOS'; plugin: GatewayPlugin; } { if (config.reporting?.type === 'hive') { + const { target, ...reporting } = config.reporting; + let usage: HiveConsolePluginOptions['usage'] = reporting.usage; + if (usage === false) { + // explicitly disabled, leave disabled + } else { + // user specified a target, extend the usage with the given target + usage = { + target, + ...(typeof usage === 'object' ? { ...usage } : {}), + }; + } return { name: 'Hive', - plugin: useMeshHive({ - ...configContext, + plugin: useHiveConsole({ logger: configContext.logger.child({ reporting: 'Hive' }), - ...config.reporting, + enabled: true, + ...reporting, + ...(usage ? { usage } : {}), ...(config.persistedDocuments && 'type' in config.persistedDocuments && config.persistedDocuments?.type === 'hive' diff --git a/packages/runtime/src/plugins/useHiveConsole.ts b/packages/runtime/src/plugins/useHiveConsole.ts new file mode 100644 index 000000000..30902e387 --- /dev/null +++ b/packages/runtime/src/plugins/useHiveConsole.ts @@ -0,0 +1,28 @@ +import type { HivePluginOptions } from '@graphql-hive/core'; +import { useHive } from '@graphql-hive/yoga'; +import { process } from '@graphql-mesh/cross-helpers'; +import type { Logger } from '@graphql-mesh/types'; +import { GatewayPlugin } from '../types'; + +export type HiveConsolePluginOptions = HivePluginOptions; + +export default function useHiveConsole< + TPluginContext extends Record = Record, + TContext extends Record = Record, +>( + options: HiveConsolePluginOptions & { logger: Logger }, +): GatewayPlugin { + const agent: HiveConsolePluginOptions['agent'] = { + name: 'graphql-hive-gateway', + logger: options.logger, + ...options.agent, + }; + // @ts-expect-error TODO: useHive plugin should inhert the TContext + return useHive({ + debug: ['1', 'y', 'yes', 't', 'true'].includes( + String(process.env['DEBUG']), + ), + ...options, + agent, + }); +} diff --git a/packages/runtime/src/types.ts b/packages/runtime/src/types.ts index b2fdad4bd..aa0998ea1 100644 --- a/packages/runtime/src/types.ts +++ b/packages/runtime/src/types.ts @@ -14,7 +14,6 @@ import type { MeshFetch, MeshPubSub, OnFetchHook, - YamlConfig, } from '@graphql-mesh/types'; import type { FetchInstrumentation, LogLevel } from '@graphql-mesh/utils'; import type { HTTPExecutorOptions } from '@graphql-tools/executor-http'; @@ -40,6 +39,7 @@ import type { UnifiedGraphConfig } from './handleUnifiedGraphConfig'; import type { UseContentEncodingOpts } from './plugins/useContentEncoding'; import type { AgentFactory } from './plugins/useCustomAgent'; import { DemandControlPluginOptions } from './plugins/useDemandControl'; +import { HiveConsolePluginOptions } from './plugins/useHiveConsole'; import { PropagateHeadersOpts } from './plugins/usePropagateHeaders'; import { UpstreamRetryPluginOptions } from './plugins/useUpstreamRetry'; import { UpstreamTimeoutPluginOptions } from './plugins/useUpstreamTimeout'; @@ -263,10 +263,16 @@ export interface GatewayHiveCDNOptions { } export interface GatewayHiveReportingOptions - extends Omit { + extends Omit< + HiveConsolePluginOptions, + // we omit this property because we define persisted documents in GatewayHivePersistedDocumentsOptions + 'experimental__persistedDocuments' + > { type: 'hive'; /** GraphQL Hive registry access token. */ token: string; + /** The target to which the usage data should be reported to. */ + target?: string; } export interface GatewayGraphOSOptions { diff --git a/yarn.lock b/yarn.lock index 577137790..e32e375b8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3904,22 +3904,7 @@ __metadata: languageName: node linkType: hard -"@graphql-hive/core@npm:0.9.1, @graphql-hive/core@npm:^0.9.0": - version: 0.9.1 - resolution: "@graphql-hive/core@npm:0.9.1" - dependencies: - "@graphql-tools/utils": "npm:^10.0.0" - "@whatwg-node/fetch": "npm:0.10.1" - async-retry: "npm:1.3.3" - lodash.sortby: "npm:4.7.0" - tiny-lru: "npm:8.0.2" - peerDependencies: - graphql: ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 - checksum: 10c0/78e22d3c2810197e5e2331ff0fef7dbb7231aa34995f59be27232a735ccc87ccd783b632e6d01d231aed47b381d12ad10a110d5c0443a446fa1917f87e3625ff - languageName: node - linkType: hard - -"@graphql-hive/core@npm:^0.10.0": +"@graphql-hive/core@npm:0.10.0, @graphql-hive/core@npm:^0.10.0": version: 0.10.0 resolution: "@graphql-hive/core@npm:0.10.0" dependencies: @@ -3943,11 +3928,11 @@ __metadata: "@envelop/generic-auth": "npm:^9.0.0" "@graphql-hive/core": "npm:^0.10.0" "@graphql-hive/logger-json": "workspace:^" + "@graphql-hive/yoga": "npm:^0.41.0" "@graphql-mesh/cross-helpers": "npm:^0.4.10" "@graphql-mesh/fusion-composition": "npm:^0.8.0" "@graphql-mesh/fusion-runtime": "workspace:^" "@graphql-mesh/hmac-upstream-signature": "workspace:^" - "@graphql-mesh/plugin-hive": "npm:^0.104.0" "@graphql-mesh/plugin-response-cache": "npm:^0.104.0" "@graphql-mesh/transport-common": "workspace:^" "@graphql-mesh/transport-rest": "npm:^0.9.0" @@ -4149,16 +4134,16 @@ __metadata: languageName: unknown linkType: soft -"@graphql-hive/yoga@npm:^0.40.0": - version: 0.40.1 - resolution: "@graphql-hive/yoga@npm:0.40.1" +"@graphql-hive/yoga@npm:^0.41.0": + version: 0.41.0 + resolution: "@graphql-hive/yoga@npm:0.41.0" dependencies: - "@graphql-hive/core": "npm:0.9.1" + "@graphql-hive/core": "npm:0.10.0" "@graphql-yoga/plugin-persisted-operations": "npm:^3.9.0" peerDependencies: graphql: ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 graphql-yoga: ^5.10.8 - checksum: 10c0/22eb19596aa846aaee494a77bb0ba8ed239194611e111835c0a12ea19504805a551563b1d34dd730007a483e364612baf7b06cf0d53d271c102deeed38c7bb28 + checksum: 10c0/d52e8234bbccdf9fadac028c53a3754bdd427317222cada1f9935b408746cfc84e1726f5def510e82668119bf7a150218e2576f12a062561ed723285faf3b316 languageName: node linkType: hard @@ -4392,23 +4377,6 @@ __metadata: languageName: node linkType: hard -"@graphql-mesh/plugin-hive@npm:^0.104.0": - version: 0.104.1 - resolution: "@graphql-mesh/plugin-hive@npm:0.104.1" - dependencies: - "@graphql-hive/core": "npm:^0.9.0" - "@graphql-hive/yoga": "npm:^0.40.0" - "@graphql-mesh/cross-helpers": "npm:^0.4.10" - "@graphql-mesh/string-interpolation": "npm:^0.5.8" - "@graphql-mesh/types": "npm:^0.104.1" - graphql-yoga: "npm:^5.12.0" - tslib: "npm:^2.4.0" - peerDependencies: - graphql: "*" - checksum: 10c0/c6e6a1c2a14b66a7245ef8b93ae4c90c283f97e3a35c609004641880f1cd5cbbf9aaa8e88698fa17e9ced61952cbfcbd9b511b65feb1051f4f4f7a82def51eae - languageName: node - linkType: hard - "@graphql-mesh/plugin-http-cache@npm:^0.105.0": version: 0.105.1 resolution: "@graphql-mesh/plugin-http-cache@npm:0.105.1"