gqty icon indicating copy to clipboard operation
gqty copied to clipboard

Potential alias collision for subscriptions

Open vicary opened this issue 1 year ago • 5 comments

When the exact same arguments are used across multiple subscriptions with different selections, GQty will generate the same query alias for them, leading to an incorrect cache update.

Consider the following case in React:

const sub1 = useSubscription();
sub1.foo({ bar: "baz" }).field1;

const sub2 = useSubscription();
sub2.foo({ bar: "baz" }).field2;

While sub1 and sub2 subscribes with a different ID in the WebSocket layer, they are stored to the cache under the same alias because of .foo({ bar: "baz" }), essentially gluing "next" message payloads for both sub1 and sub2.

The solution is to generate a unique alias for each subscription.


Thanks @jsjs_dev for reporting in Discord.

vicary avatar Oct 29 '24 11:10 vicary

Hey @vicary, do you have any updates on this collision issue?

Our caching is causing collisions on our queries, not using subs. Is there a way to update the alias internally so it generates IDs instead of just hashing the arguments?

this is running on NextJS server components, btw, using the resolve

brummetj avatar Apr 10 '25 17:04 brummetj

@brummetj custom alias is not possible at the moment because the design of the core does not support it, but it's definitely a good escape hatch to have.

I believe this issue is shared by all normalized GraphQL cache available out there, that's where GraphQL fragments become useful. You may achieve the same by creating a query hook that pre-selects all fields used in your apps.

import { useQuery } from "~/gqty";

export const useQueryFoobar = () => {
  const query = useQuery();

  const {
    foo, // used by component A
    bar, // used by component B
  } = query;

  return query;
};

// ~/components/A.tsx
export default function A() {
  const query = useQueryFoobar();

  return <>{query.foo}</>;
}

// ~/components/B.tsx
export default function B() {
  const query = useQueryFoobar();

  return <>{query.bar}</>;
}

vicary avatar Apr 11 '25 05:04 vicary

@vicary ahh interesting. will this help with collision on nextJS?

maybe a little more info.. The issue we are seeing when two users hit the browser at the same time, the cache will reuse another users cache and they will see each other's data, which can contain PII. we were able to reproduce it by having two users login at the same time.

I was able to work around it by adding unique session id arguments to the queries/mutations due to your comment on this issue about using exact same arguments for queries.

any thoughts on how we can avoid the collisions without using ID fields in the arguments?

example

type Query {
  foo(id: ID): bar -> won't cause collision
  foo: bar -> will cause data collisions and users can see another user data
}

brummetj avatar Apr 14 '25 15:04 brummetj

@brummetj Interesting use case for the same app to support two concurrent user sessions, if that's what you mean by "at the same time".

For most apps, calling cache.clear(); when a user logs out is sufficient.

vicary avatar Apr 14 '25 18:04 vicary

@vicary oh not the same user... different users... user 1 would see user 2 data when they both hit the browser simultaneously.

brummetj avatar Apr 22 '25 15:04 brummetj