playhouse icon indicating copy to clipboard operation
playhouse copied to clipboard

[GraphQL Plugin] Add ability for post-/pre- processing values

Open wKich opened this issue 3 years ago • 0 comments

Motivation

Some times you want to modify a value that comes from the backstage catalog. You probably would add a resolver for specific field, but you couldn't. It's because if you use @field or @relation directives for that field, the graphql plugin will add a resolver and your resolver will conflict with it.

Approach

We can add pre and post hooks that duplicate schema structure:

const hooks = {
  pre: {
    Entity: (entity: Entity) => ({ ...entity, id: encodeEntityId(entity) }),
  post: {
    Component: {
      tag: (tag: string, entity: Entity) => ({ id: encodeId({ key: 'tag.label', value: tag }), label: tag })
    }
  }
}

Here you can see that in pre.Entity hook we add new field id to the entity. There is no reason to add field specific pre-hooks. But maybe it should be better to rename them to interfaces and fields hooks. For the post.Component.tag field we change a type from string to { id: string, label: string } and then new value is used as a result of resolver. The idea is use pre hook for adding/removing fields from source object and post for transforming a specific field value.

With graphql best practice fetching data at field level https://medium.com/paypal-tech/graphql-resolvers-best-practices-cd36fdbcef55 it makes difficult to use post hooks on fields with @relation directives. And pre hooks are more suitable for this case.

Alternative approach

Instead of having that structure we can allow to pass an array of transformers:

const preHooks = [
  {
    predicate: (interface: string, entity: Entity) => interface == 'Entity', /* isSpecificEntity(entity) */
    transform: (entity: Entity) => ({ ...entity, id: encodeEntityId(entity) })
  }
]
const postHooks = [
  {
    predicate: (field: string, interface: string, entity: Entity) => interface == 'Component' && field == 'tag',
    transform: (value: any, entity: Entity) => ({ id: encodeId({ key: 'tag.label', value: tag }), label: tag })
  }
]

I'd like the first variant, but, the second one is more flexible, it allows apply transformations according a data.

wKich avatar Nov 16 '22 14:11 wKich