Skip to content

Tag type discrimination POC#98

Closed
stackoverfloweth wants to merge 4 commits into
mainfrom
tag-kind-record
Closed

Tag type discrimination POC#98
stackoverfloweth wants to merge 4 commits into
mainfrom
tag-kind-record

Conversation

@stackoverfloweth
Copy link
Copy Markdown
Member

@stackoverfloweth stackoverfloweth commented May 8, 2026

This PR enables a method chaining of tags to produce descendant tags. This enables tags to have a shared ancestor and enforce that descendants satisfy the type of the parent.

const genericTag = tag<{ id: string }>()

type User = { id: string, name: string, email: string }
const userTag = genericTag.add<User>()

type UserAvatar = User & { imageUrl: string }
const userAvatarTag = userTag.add<UserAvatar>()

const userQuery = query(getUser, [userId], { tags: [userTag] })
const userAvatarQuery = query(getUserAvatar, [userId], { tags: [userAvatarTag] })

When invalidating queries, devs can target whatever layer they want and it will trickle down

// only invalidates the avatar queries
client.invalidateQueries(userAvatarTag)

// invalidates the user queries and avatar queries
client.invalidateQueries(userTag)

// invalidates all 3
client.invalidateQueries(genericTag)

When performing an optimistic update, again devs can target whatever layer they want and it will trickle down. Because descendants must satisfy the constraints of the parent, we should be safe from corrupting cache with bad data.

// updates both user cache AND avatar cache with user data
client.setQueryData(userTag, (data) => {
  return { ...data, ...updates }
})

Warning

What this DOESN'T solve for (why this is draft) is when the parent is a union of unrelated types we're back in the world where you can setCache at the parent and corrupt cache.

const genericTag = tag<User | Potato>()
const userTag = genericTag.add<User>()
const potatoTag = genericTag.add<Potato>()

setQueryData(genericTag, (data) => {
  // corrupts the potato cache 😢 
  return { id: '123', name: 'John Doe', email: 'john.doe@example.com' }
})

We could solve for this by forcing unions to instead be an intersection. That would mean that if the types are truly > interoperable they can share an optimistic update but if they're not they can only be invalidated together.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant