Skip to content

[core] New plugin data API #2404

Open
Open
@EmrysMyrddin

Description

@EmrysMyrddin

Description

This PR is a proposition to solve a long standing DX issue: sharing data between ALL hooks of a plugin.

Context

Today, the only relyable way to share data between the hooks of a plugin is to use WeakMap with the GraphQL context as key:

function usePlugin(): Plugin {
  new dataByContext = new WeakMap<any, { foo: string }>();
  return {
    onParse({ context }) {
      dataByContext.set(context, { foo: 'bar' );
    },
    onValidate({ context }) {
      const data = dataByContext(context);
      if(data) { // We even have to check for its existence, even it should always be defined.
        console.log(data)
      }
    }
}

In Yoga, it's even worse since in most Yoga specific hooks, the graphql context is not built yet, so the only thing shared by all hooks is the HTTP request:

function usePlugin(): YogaPlugin {
  new dataByRequest = new WeakMap<Request, { foo: string }>();
  return {
    onRequestParse({ request }) {
      dataByRequest.set(request, { foo: 'bar' );
    },
    onValidate({ context }) {
      const data = dataByRequest(context.request);
      if(data) { // We even have to check for its existence, even it should always be defined.
        console.log(data)
      }
    }
}

But this can be misleading: If batching is enabled, multiple different operation will share the same data. We currently have multiple plugins that are using this technique and that are open to bugs when batching is enabled.

Proposition

My proposal is to add a dedicated API directly in the core of Envelop, Yoga and Hive to have a streamlined way of sharing data between hooks. While the WeakMap trick works, it's really not very good DX, and is very error prone.

The API would be a data field that will be present in ALL hooks (for Envelop hooks, but also Yoga and Hive hooks).
To emphases the fact that there is potentially multiple level of data sharing (operation wide, http request wide, subgraph execution wide), this data object would contain a field for each scope:

type EnvelopData = {
  forOperation: object
}

type YogaData = EnvelopData & {
  forRequest: object
}

type HiveData = YogaData & {
  forSubgraphExecution: object
}

This data would then be available in all hooks, but depending of the hook, not all scopes will be available.

function myPlugin(): HivePlugin {
	return {
      onRequestParse({ data }) {
        // only data.forRequest is available
      },
      onExecute({ data }) {
        // both data.forRequest and data.forOperation are available
      },
      onSubgraphExecute({ data }) {
        // all scopes are available: data.forRequest, data.forOperation and data.forSubgraphExecution
      },
}

Of course, it will also imply adding generic types everywhere. And this is probably a bit tricky because it will imply adding more generics to existing Plugin type, which I'm not sure if it is non-breaking or not.

As a proof of concept, I've implemented the first step of this proposal: the Envelop scope (#2405)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions