Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DO NOT MERGE: (WIP) Adds support for Link preprocessing #10290

Closed
wants to merge 1 commit into from

Conversation

nyteshade
Copy link

@nyteshade nyteshade commented Nov 16, 2022

For users of the @apollo/client that use client side schemas, this PR allows the users to provide an ApolloLink for preprocessing that allows configurable modification to the query/mutation DocumentNode before the client performs its own caching and execution.

An example of modifying the DocumentNode to swap a @Persistent directive for a @client directive when the application is offline, or removing it entirely if it online.

RFC: #10228

const client = new ApolloClient({
  cache,
  uri: 'http://someserver.com/graphql',
  preprocessorLink: new ApolloLink((operation) => {
    let newOpSDL = parse(print(operation.query))

    newOpSDL = swapDirective(
      newOpSDL,
      'persistent',
      () => online() ? undefined : 'client'
    )

    return { data: newOpSDL }
  })
})

client.query({ query: gql`query { your { heart { away }}}`})
client.mutate({ mutation: gql`mutation { or { change { things }}}`})

 function swapDirective(document, searchFor, replaceWith) {
  let alteredDocument = document

  const isFn = (o) => /Function/.test(Object.prototype.toString.call(o))
  const target = isFn(searchFor) ? searchFor() : String(searchFor)
  const newValue = isFn(replaceWith) ? replaceWith() : replaceWith && String(replaceWith) | undefined

  const getter = (n) => n?.name?.value || null
  const setter = (n, v) => { if (n?.name) n.name.value = v; return n }
  const hasTarget = (n) => new RegExp(target, 'i').test(getter(n))

  alteredDocument = visit(document, {
    Field: {
      enter(node, key, parent, path, ancestors) {
        if (node.directives?.length && node.directives.some(hasTarget)) {
          if (!!!newValue) {
            for (let i = 0; i < node.directives.length; i++) {
              if (new RegExp(target, 'i').test(getter(node.directives[i]))) {
                node.directives.splice(i, 1)
                i--
              }
            }
          }
          else {
            // Thought process here is to swap @persistent for @client
            // when we are offline.
            node.directives = node.directives
              .filter(directive => new RegExp(target, 'i').test(getter(directive)))
              .map(directive => {
                return setter(directive, newValue)
              })
          }
          return node
        }
      },
    }
  })

  return alteredDocument || document
}

Checklist:

  • If this PR is a new feature, please reference an issue where a consensus about the design was reached (not necessary for small changes)
  • Make sure all of the significant new logic is covered by tests

For users of the @apollo/client that use client side schemas, this PR
allows the users to provide an ApolloLink for preprocessing that allows
configurable modification to the query/mutation DocumentNode before
the client performs its own caching and execution.

An example of modifying the DocumentNode to swap a @Persistent directive
for a @client directive when the application is offline, or removing it
entirely if it online.

```js
const client = new ApolloClient({
  cache,
  uri: 'http://someserver.com/graphql',
  preprocessorLink: new ApolloLink((operation) => {
    let newOpSDL = parse(print(operation.query))

    newOpSDL = swapDirective(
      newOpSDL,
      'persistent',
      () => online() ? undefined : 'client'
    )

    return { data: newOpSDL }
  })
})

client.query({ query: gql`query { your { heart { away }}}`})
client.mutate({ mutation: gql`mutation { or { change { things }}}`})

 function swapDirective(document, searchFor, replaceWith) {
  let alteredDocument = document

  const isFn = (o) => /Function/.test(Object.prototype.toString.call(o))
  const target = isFn(searchFor) ? searchFor() : String(searchFor)
  const newValue = isFn(replaceWith) ? replaceWith() : replaceWith && String(replaceWith) | undefined

  const getter = (n) => n?.name?.value || null
  const setter = (n, v) => { if (n?.name) n.name.value = v; return n }
  const hasTarget = (n) => new RegExp(target, 'i').test(getter(n))

  alteredDocument = visit(document, {
    Field: {
      enter(node, key, parent, path, ancestors) {
        if (node.directives?.length && node.directives.some(hasTarget)) {
          if (!!!newValue) {
            for (let i = 0; i < node.directives.length; i++) {
              if (new RegExp(target, 'i').test(getter(node.directives[i]))) {
                node.directives.splice(i, 1)
                i--
              }
            }
          }
          else {
            // Thought process here is to swap @Persistent for @client
            // when we are offline.
            node.directives = node.directives
              .filter(directive => new RegExp(target, 'i').test(getter(directive)))
              .map(directive => {
                return setter(directive, newValue)
              })
          }
          return node
        }
      },
    }
  })

  return alteredDocument || document
}
```
@apollo-cla
Copy link

@nyteshade: Thank you for submitting a pull request! Before we can merge it, you'll need to sign the Apollo Contributor License Agreement here: https://contribute.apollographql.com/

@jpvajda jpvajda linked an issue Nov 16, 2022 that may be closed by this pull request
if (modifiedMutation) {
mutation = modifiedMutation;
}

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What I don't understand is why this await within QueryManager.mutate is causing so many tests to fail. Logic says it shouldn't otherwise cause such issues.

@jerelmiller
Copy link
Member

Per this comment, we plan to do this work as part of #10303. Thanks for the discussion and helping us think through a solution!

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Feb 1, 2023
@apollographql apollographql unlocked this conversation Feb 1, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

RFC: Query/Mutation/Subscription Pre-Processor
4 participants