Giter VIP home page Giter VIP logo

Comments (17)

bennypowers avatar bennypowers commented on May 22, 2024 41

My implementation, based on @Zn4rK's work, for avoiding ' You are calling concat on a terminating link, which will have no effect` while still using Batching etc.

import { getMainDefinition } from 'apollo-utilities';
import { split } from 'apollo-link';
import { httpLink } from './httpLink.js';
import { wsLink } from './wsLink.js';
import { uploadLink } from './uploadLink.js';

const isFile = value => (
  (typeof File !== 'undefined' && value instanceof File) ||
  (typeof Blob !== 'undefined' && value instanceof Blob)
);

const isUpload = ({ variables }) =>
  Object.values(variables).some(isFile);

const isSubscriptionOperation = ({ query }) => {
  const { kind, operation } = getMainDefinition(query);
  return kind === 'OperationDefinition' && operation === 'subscription';
};

const requestLink = split(isSubscriptionOperation, wsLink, httpLink);

export const terminalLink = split(isUpload, uploadLink, requestLink);

from apollo-upload-client.

jaydenseric avatar jaydenseric commented on May 22, 2024 4

That error makes sense, it looks like you are concatenating the links in the wrong order. apollo-upload-client is a "terminating" link; that means no link can be concatenated after because it is the end of the road for sending the request off.

from apollo-upload-client.

Tanapruk avatar Tanapruk commented on May 22, 2024 3

The omitTypename with JSON.parse is too aggressive that it deleted the File, so when forwarding to the uploadLink, it disappeared.

function omitTypename(key, value) {
    return key === '__typename' ? undefined : value
}

const cleanTypenameFieldLink = new ApolloLink((operation, forward) => {
    if (operation.variables) {
        operation.variables = JSON.parse(JSON.stringify(operation.variables), omitTypename)
    }

    return forward(operation)
});

My fix is to use a less aggressive omit __typename function with a check for file uploading first.

const isFile = value => (
  (typeof File !== 'undefined' && value instanceof File) ||
  (typeof Blob !== 'undefined' && value instanceof Blob)
);

//borrow from https://gist.github.com/Billy-/d94b65998501736bfe6521eadc1ab538
function omitDeep(value, key) {
  if (Array.isArray(value)) {
    return value.map((i) => omitDeep(i, key))
  } else if (typeof value === 'object' && value !== null && !isFile(value)) {
    return Object.keys(value).reduce((newObject, k) => {
      if (k == key) return newObject
      return Object.assign({ [k]: omitDeep(value[k], key) }, newObject)
    }, {})
  }
  return value
}


function createOmitTypenameLink() {
  return new ApolloLink((operation, forward) => {
    if (operation.variables) {
      operation.variables = omitDeep(operation.variables, '__typename')
    }
    return forward(operation)
  })
}

const uploadLink = createUploadLink({
  uri: GRAPHQL_URL
})

const createApolloClient = (cache = {}) => {
  return new ApolloClient({
    connectToDevTools: process.browser,
    ssrMode: typeof window !== 'undefined',
    cache: new InMemoryCache().restore(cache),
    link: ApolloLink.from([omitTypenameLink, uploadLink])
  })
}

from apollo-upload-client.

flemwad avatar flemwad commented on May 22, 2024 1

@dbelchev I'm aware you can concat in that manner too. I tried that and it yielded the same result:

import cleanTypenameFieldLink from './utils/cleanTypenameFieldLink';
const uploadLink = createUploadLink({ uri });

// Apollo client
const client = new ApolloClient({
    link: ApolloLink.from([cleanTypenameFieldLink, uploadLink]),
    cache: new InMemoryCache(),
    credentials: 'same-origin' //TODO: Make this more stringent on prod
});

Which is a un-promised preview file on the server:

upload:
   { preview: 'blob:http://localhost:3333/3a4284ce-0ffd-4eed-b80b-3a6262d7ee3a' }

or upload simply ends up null as shown below.

The only thing I can think is that I'm using the scalar Upload type as a nested property on a larger input type at this point:

export const PhotoInput = `
    scalar Upload

    input PhotoInput {
        id: String!
        postName: String!
        whatToDo: String!
        unixTime: Int!
        bounty: Float
        upload: Upload!
        meta: PhotoMetaInput!
    }
`;

Here's what my object looks like before sending:
upload-client

Note that this input works when un-composed, and doesn't include the "__typename" prop.

Here's the same object console.logged in an extremely dumb link function showing that the photoInput upload property has changed:

export default new ApolloLink((operation, forward) => {
    console.log('after', operation);
    return forward(operation)
});

afterlink

At this point, depending on what the "dumb" link does, upload either ends up: null or just a preview blob, and I can't execute the promise on the server.

from apollo-upload-client.

flemwad avatar flemwad commented on May 22, 2024 1

This was an issue with my own link. Closing, and I apologize for any confusion.

from apollo-upload-client.

g-borgulya avatar g-borgulya commented on May 22, 2024 1

I have exactly the same issue, no idea how to figure out the cause of the problem.
Any ideas plesae how to start solving such an issue? It seems it could be valuable for others as well.

Maybe it would be also very useful if someone could provide an example how to correctly create a complex link both on the client and using ssr, and how to use it if the graphql schema is a bit more complex.

from apollo-upload-client.

smithaitufe avatar smithaitufe commented on May 22, 2024 1

I decided to avoid this package. I send base64 of whatever I want to upload to the server.

from apollo-upload-client.

acomito avatar acomito commented on May 22, 2024 1

I'm trying to figure this one out. Would love some help if anyone notices an obvious blunder below


import {ApolloClient} from 'apollo-client';
import {ApolloLink} from 'apollo-link';
import {InMemoryCache} from 'apollo-cache-inmemory';
import fetch from 'isomorphic-fetch'; // https://github.com/apollographql/apollo-link/issues/513#issuecomment-368234260
import apolloLogger from 'apollo-link-logger';
import {authLink, httpLink} from './links';
import errorLink from './links/errorLink';
import {createUploadLink} from 'apollo-upload-client';

// create apollo cache
const cache = new InMemoryCache({});

const uploadLink = createUploadLink({
  uri: !process.env.REACT_APP_API_HOST,
  headers: {
    'keep-alive': 'true',
  },
});

let links = [authLink, errorLink, httpLink, uploadLink];

if (process.env.NODE_ENV === 'development') {
  links = [apolloLogger, authLink, errorLink, httpLink, uploadLink];
}

// create apollo-client instance
const client = new ApolloClient({
  link: ApolloLink.from(links),
  fetch,
  cache,
  connectToDevTools: process.env.NODE_ENV === 'development',
});

// export the client to be used by the app
export default client;

everything is working until I add uploadLink

UPDATE: All I had to do was remove my httpLink which I guess is doing the same thing as uploadLink

from apollo-upload-client.

flemwad avatar flemwad commented on May 22, 2024

As mentioned in 1) and 2) I've tried it both ways. With uploadLink last as well.

It appears to me what's happening is:

When I don't compose it and just use the straight up uploadLink

const uploadLink = createUploadLink({ uri });

const client = new ApolloClient({
    link: uploadLink,
    cache: new InMemoryCache(),
    credentials: 'same-origin' //TODO: Make this more stringent on prod
});

I'm getting an upload "Promise" on the server side, which then lets me resolve the stream from it:

upload:
   Promise {
     { stream: [Object],
     filename: 'cool2.png',
     mimetype: 'image/png',
     encoding: '7bit' } }

But, when I compose the link with my custom link:

import cleanTypenameFieldLink from './utils/cleanTypenameFieldLink';
const uploadLink = createUploadLink({ uri });
const appLink = concat(cleanTypenameFieldLink, uploadLink);

// Apollo client
const client = new ApolloClient({
    link: appLink,
    cache: new InMemoryCache(),
    credentials: 'same-origin' //TODO: Make this more stringent on prod
});

The response on my server I just get a "preview" file upload, and no promise to resolve:

upload:
   { preview: 'blob:http://localhost:3333/e514498c-3031-403c-96ea-349b2bd120d7' }

I'm not sure if I did something wrong? But before I wrote my code depending on a promise being sent to the server. So that's breaking.

from apollo-upload-client.

dbelchev avatar dbelchev commented on May 22, 2024

@flemwad, I assume that you have other problem here, but according to Apollo-link docs you can compose the links as follows:
let link = ApolloLink.from([errorLink, middlewareLink, apolloUploadLink]);
The above is working for me in my project without any problem with apolloUploadLink.

from apollo-upload-client.

smithaitufe avatar smithaitufe commented on May 22, 2024

@flemwad Please how did you resolve your issue? If you don't mind, can you look at this #64

Currently, I can't even do a post to my server.

from apollo-upload-client.

kojuka avatar kojuka commented on May 22, 2024

yes @flemwad I'm getting the same thing. please let us know how you fixed it.

from apollo-upload-client.

bennypowers avatar bennypowers commented on May 22, 2024

I have a requestLink which routes requests depending on if they are subscriptions or queries, which is a terminating link. That means that I have to choose between using uploadLink and requestLink? Is it not possible to use this package with another terminating link?

from apollo-upload-client.

jaydenseric avatar jaydenseric commented on May 22, 2024

@bennypowers you can't have 2 terminating links, because, well, they are terminating. The last one will never happen! You can use the Apollo link .split() method to create a fork on some sort of logic to choose which link to terminate with.

from apollo-upload-client.

norbertkeri avatar norbertkeri commented on May 22, 2024

I think it would be worthwhile to mention in the README that you can use .split() in combination with upload-client, to keep batch working for your regular queries, I think that's what the major usecase people are looking for, not the batching of the uploads themselves.

from apollo-upload-client.

mehmetnyarar avatar mehmetnyarar commented on May 22, 2024

I have this issue currently and tried to solve it by using @bennypowers 's method. Since my mutations are sometimes objects, i've used lodash's isPlainObject method to determine the terminal link.

const requestLink = split(
  ({ query }) => {
    const { kind, operation }: any = getMainDefinition(query)
    console.log('requestLink', { query, kind, operation })
    return kind === 'OperationDefinition' && operation === 'subscription'
  },
  wsLink,
  httpLink
)
const uploadLink = createUploadLink({ uri: BACKEND_GQL })
const isFile = (value: any) => {
  if (isPlainObject(value)) return Object.values(value).map(isFile).includes(true)
  const isfile = typeof File !== 'undefined' && value instanceof File
  const isblob = typeof Blob !== 'undefined' && value instanceof Blob
  return isfile || isblob
}
const isUpload = ({ variables }) => Object.values(variables).some(isFile)
const terminalLink = split(isUpload, uploadLink, requestLink)
const link = ApolloLink.from([
  authLink,
  errorLink,
  terminalLink
])
export const client = new ApolloClient({
  link,
  cache
})

The topic was closed and a little bid old. Therefore I'd like to ask if there is any update to this or any other solution, or more easy way? Because for each request I have make this decision, and 99% of the operations are not related to upload.

from apollo-upload-client.

fresonn avatar fresonn commented on May 22, 2024

@acomito, I got the same issue, but the solution was on README in installation section.
Quote:

Apollo Client can only have 1 terminating Apollo Link that sends the GraphQL requests; if one such as HttpLink is already setup, remove it.

It seems to better read the docs first for all of us 😂

from apollo-upload-client.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.