ardatan/graphql-mesh

Expose `getCacheInstanceFromConfig` and `getCacheInstanceFromConfig`

klippx opened this issue · 5 comments

Is your feature request related to a problem? Please describe.

When running Hive Gateway programmatically, and you want access to some "Built In" plugins, the fact that these are built in to the serve-cli is making things a bit hard.

Describe the solution you'd like

We need these functions exposed so we can recreate what hive-gateway supergraph does:

import {
  type GatewayCLIConfig,
  getBuiltinPluginsFromConfig,
  getCacheInstanceFromConfig,
} from '@graphql-mesh/serve-cli';

export const generateGatewayConfig = async (): Promise<GatewayCLIConfig> => {
  // Cache creation is only part of config when using for serve-cli, see node_modules/@graphql-mesh/serve-cli/esm/config.js
  const cacheConfig = createCacheConfig();
  const pubsub = new PubSub();
  const cache = await getCacheInstanceFromConfig(
    { cache: cacheConfig },
    {
      pubsub,
      logger: log,
    },
  );

  // Plugin creation are only part of config when using for serve-cli, see node_modules/@graphql-mesh/serve-cli/esm/config.js
  // For programmatic hive-gateway usage, we need to define these plugins in the config manually:
  const builtinPlugins = await getBuiltinPluginsFromConfig(
    {
      jwt: createJwtConfig(),
      openTelemetry: createOpenTelemetryConfig(),
      prometheus: createPrometheusConfig(),
    },
    {
      cache,
    },
  );

  return defineConfig({
    supergraph: createSupergraph(),
    graphiql: false,
    logging: log,
    pubsub,
    cache,
    plugins() {
      return builtinPlugins;
    },
  });
};

Describe alternatives you've considered

  • Copy these files locally
  • Apply patch commit
  • In the future, Hive maintainers might want to move some of the non-CLI functionality to some other place other than serve-cli

Those are internal functions we use to process CLI configuration. I believe it might be harder for us to maintain it as a public API since they can have breaking changes in the future. Also those functions are intended to work only with CLI. They won't work as expected in bundling environments since they are not supporting dynamic imports etc. If we provide them as public API, it will be confusing for the users that deploy Hive GW to non-Node envs etc.
But instead, you can find all those cache implementations and plugins exported from the package.

I see the concern. And I appreciate you trying to maintain sanity and keeping confusion at bay.

Those are internal functions we use to process CLI configuration.

This might explain our different views. "CLI" configuration? Isn't it more like "Hive Gateway" configuration?

I think of it as Hive Gateway configuration, and that's precisely why I believe it should be exposed. So my programmatic usage also works according to the config documented over at https://the-guild.dev/graphql/hive/docs/gateway/

@graphql-hive/gateway vs @graphql-mesh/serve-cli

If we think of this as Hive Gateway configuration, wouldn't you agree that using createGatewayRuntime(config: GatewayConfig) from @graphql-hive/gateway should work the same if you use the serve cli, since this config is "Hive Gateway" configuration?

If you agree, then you will soon realize that generateGatewayConfig and getBuiltinPluginsFromConfig comes into play in order to correctly honor the config you pass to it.

This is because of

  return defineConfig({
    // Cache creation is only part of config when using serve-cli
    cache,
    pubsub,
    // Built-in plugins is only part of config when using serve-cli
    plugins() {
      return builtinPlugins; # GatewayPlugin[]
    },
  })

Which uses getBuiltinPluginsFromConfig, and needs cache as argument which in turn needs getCacheInstanceFromConfig. And getBuiltinPluginsFromConfig also needs the config

Unfortunately this code today just happen to live in @graphql-mesh/serve-cli

Suggestion 1

Perhaps config parsing can be exposed as a different package such as @graphql-mesh/config-parsing to avoid exposing it via @graphql-mesh/serve-cli.

That way you can choose to expose getCacheInstanceFromConfig and getBuiltinPluginsFromConfig from there, or even better, expose something that you can use for the plugins and "friends" options cache, pubsub:

import { parseBuiltinPluginsFromConfiguration } from '@graphql-mesh/config-parsing' # or whatever
  # ...
  return defineConfig({
    # will generate `plugins()`, `cache` and `pubsub` or whatever it needs to generate
    ...parseBuiltinPluginsFromConfiguration({
      jwt: jwtConfig,
      openTelemetry: openTelemetryConfig,
      prometheus: prometheusConfig,
      rateLimiting: rateLimitingConfig,
      # etc
    }),
  })

And then @graphql-mesh/serve-cli will use this parseBuiltinPluginsFromConfiguration function instead of doing it "manually". And then it will also be reusable for our programmatic usage.

Suggestion 2

What about my suggestion here to expose them as experimental? #7765 (comment)

That way you cover your self from breaking the API, while still enabling programmatic usage.

I am not too fond of this idea myself, I think Suggestion 1 above makes way more sense.

@graphql-mesh/serve-cli has been deprecated and renamed to @graphql-hive/gateway. They are supposed to be identical.

I'd love to know why you need programmatic usage instead of CLI which should have the same functionality. What is missing in CLI so you need programmatic API? Programmatic API is a solution not for regular Node users that can run CLI as-is but the users that cannot run CLI but need to deploy their gateway to a lambda environment, Deno, Cloudflare Workers or some other Node-incompatible environment.

Those functions are intended to consume the CLI Config running on Node in a way that CLI runs.
For example, if you use an environment that requires bundling etc so that dynamic imports and CLI specific(mostly Node specific), those function won't work as expected. That's why, we have seperate programmatic approach for this use-case.
If you have an environment that you pass a bundled single file with all your app inside + createGatewayRuntime aka programmatic approach then when you use those functions, it won't work as expected because of dynamic imports and so on such as lamdbas, Cloudflare Workers and so on.
https://the-guild.dev/graphql/hive/docs/gateway/deployment/serverless#bundling-problem
If we expose them as part of the public API for programmatic usage, we cannot support this use case which would be consuming and we want to avoid this kind of confusion but let user to import those plugins and so on, and pass those by themselves. And those plugin exports have exactly the same API we have in CLI config.

The main reason for that was so we can apply our own opentelemetry injection and customization thereof. But I think I might have to fold my hand, it might not be as important as I thought, as tracing and metrics work well enough using the CLI. Especially now that initializeNodeSDK is available, we can still initialize it ourselves. So, my decision will be to try to not pursue programmatic usage but instead try to make do with CLI.

Thanks for the discussion, I think it is better to close this for now and let's get back to it in the future if I can no longer use the CLI.

Actually, I think I might have found the issue now

If I want to use Bun runtime - and that is a requirement if we want to replace a rust router - then I have to use this programmatic approach: https://the-guild.dev/graphql/hive/docs/gateway/deployment/runtimes/bun

And then we have a problem: Tracing wont work. I started a discussion here to not re-open issue immediately, and to keep the noise low #7901