Skip to content

Conversation

@jerelmiller
Copy link
Member

@jerelmiller jerelmiller commented Apr 16, 2025

This would help with #12555

This PR adds the ability to detect if an error was emitted by the link chain. This can be useful if your application uses custom errors in other areas of the codebase that would otherwise get difficult to differentiate. Use NetworkError.is to detect if an error is the link chain.

Take this example:

function updateUser(params) {
  if (!params.email) {
    throw new Error("Email must be present");
  }

  return client.mutate({ mutation, variables: params });
}

updateUser().catch((error) => {
  // is the `error` the validation error or an error from the 
  // network?
})

Since Apollo Client 4.0 no longer wraps errors by default, it is near impossible to distinguish whether the error came from the link chain or the custom error in the function.

By introducing this new helper, the check becomes:

function updateUser(params) {
  if (!params.email) {
    throw new Error("Email must be present");
  }

  return client.mutate({ mutation, variables: params });
}

updateUser().catch((error) => {
  if (LinkError.is(error)) {
    // This is an error from the link chain
  }

  // The error was my validation error
})

@changeset-bot
Copy link

changeset-bot bot commented Apr 16, 2025

🦋 Changeset detected

Latest commit: 6ba252b

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@apollo/client Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@svc-apollo-docs
Copy link

svc-apollo-docs commented Apr 16, 2025

⚠️ Docs preview not attached to branch

The preview was not built because the PR's base branch release-4.0 is not in the list of sources.

An Apollo team member can comment one of the following commands to dictate which branch to attach the preview to:

  • !docs set-base-branch version-2.6
  • !docs set-base-branch main

Build ID: abb248ef9fc975ee012db28e

@pkg-pr-new
Copy link

pkg-pr-new bot commented Apr 16, 2025

npm i https://pkg.pr.new/@apollo/client@12561

commit: 6ba252b

import type { ErrorLike } from "@apollo/client";
import { CombinedGraphQLErrors } from "@apollo/client";

const registry = new WeakSet();
Copy link
Member

Choose a reason for hiding this comment

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

Debating with myself if we can find a way around this global.
Even though it's a WeakSet, this might become a memory hog if WeakSet were ever to be polyfilled in a non-weak way for a runtime that didn't support it, and in a long-running process that would mean that all errors accumulate indefinitely.
My first thought would be to add this as a property on ApolloClient, but that way, the NetworkError.is api wouldn't work anymore, but it would have to be something like client.isNetworkError. Not the end of the world, but not very consistent either.

I'll experiment around for a bit.

@phryneas
Copy link
Member

Important note: this introduces a circularity that needs to be fixed:

❌ Circular dependencies found for customConditions: []
[
  [
    'src/core/index.ts',
    'src/core/ApolloClient.ts',
    'src/core/QueryManager.ts',
    'src/errors/index.ts',
    'src/errors/NetworkError.ts'
  ]
]

Comment on lines 4 to 9
const registry = new WeakSet();

/** @internal Please do not use directly. */
export function registerNetworkError(error: ErrorLike) {
if (!CombinedGraphQLErrors.is(error)) {
registry.add(error);
Copy link
Member

@phryneas phryneas Apr 17, 2025

Choose a reason for hiding this comment

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

Suggested change
const registry = new WeakSet();
/** @internal Please do not use directly. */
export function registerNetworkError(error: ErrorLike) {
if (!CombinedGraphQLErrors.is(error)) {
registry.add(error);
const registry = new WeakSet();
const networkErrorSymbol = Symbol.for("apollo.networkError");
/** @internal Please do not use directly. */
export function registerNetworkError(error: ErrorLike) {
if (!CombinedGraphQLErrors.is(error)) {
if (Object.isExtensible(error)) {
Object.defineProperty(error, networkErrorSymbol, {
value: true,
enumerable: false,
writable: false,
configurable: true,
});
} else {
registry.add(error);
}

(together with)

  is: (error: unknown) =>
    (error && (error as any)[networkErrorSymbol]) ||
    registry.has(error as ErrorLike),

Something like this could be a memory-defensive version that only uses registry as a last resort and tries to "mark" errors first.

On the other hand, I might be way overhinking this, too. Environments where WeakSet needs to be polyfilled are probably not that common, and where I can imagine they exist (e.g. edge runtimes), they probably also are not very long-lived.

We really need a list of alternative JS runtimes and their features at some point.

Copy link
Member

Choose a reason for hiding this comment

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

I've made a list of engines and their support and looking at that I believe we can safely assume that WeakSet is available and probably don't need these workarounds. Also, this workaround could also be done by a WeakSet polyfill, so I'd say we can skip this concern.
image

Copy link
Member Author

Choose a reason for hiding this comment

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

Let's keep this in our back pocket. If we end up in situations where the WeakSet becomes a problem, using that defensive fallback is a great solution.

@jerelmiller jerelmiller changed the title Add a new link that wraps errors in NetworkError instances Add a new helper that detects network errors from the link chain Apr 17, 2025
Base automatically changed from jerel/is-helpers to release-4.0 April 17, 2025 15:08
@jerelmiller jerelmiller force-pushed the jerel/network-error-link branch from 7027d12 to 3692d1f Compare April 17, 2025 15:23
@netlify
Copy link

netlify bot commented Apr 17, 2025

Deploy Preview for apollo-client-docs ready!

Name Link
🔨 Latest commit 6ba252b
🔍 Latest deploy log https://app.netlify.com/sites/apollo-client-docs/deploys/68013b35c5e359000743ee34
😎 Deploy Preview https://deploy-preview-12561--apollo-client-docs.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify site configuration.

@jerelmiller jerelmiller force-pushed the jerel/network-error-link branch from 3692d1f to 9140dba Compare April 17, 2025 17:05
@github-actions
Copy link
Contributor

github-actions bot commented Apr 17, 2025

size-limit report 📦

Path Size
import { ApolloClient, InMemoryCache, HttpLink } from "@apollo/client" (CJS) 42.42 KB (+0.4% 🔺)
import { ApolloClient, InMemoryCache, HttpLink } from "@apollo/client" (production) (CJS) 37.97 KB (+0.3% 🔺)
import { ApolloClient, InMemoryCache, HttpLink } from "@apollo/client" 32.43 KB (-0.05% 🔽)
import { ApolloClient, InMemoryCache, HttpLink } from "@apollo/client" (production) 27.37 KB (-0.02% 🔽)
import { ApolloProvider } from "@apollo/client/react" 5.19 KB (0%)
import { ApolloProvider } from "@apollo/client/react" (production) 962 B (0%)
import { useQuery } from "@apollo/client/react" 10.92 KB (0%)
import { useQuery } from "@apollo/client/react" (production) 6.67 KB (0%)
import { useLazyQuery } from "@apollo/client/react" 6.36 KB (0%)
import { useLazyQuery } from "@apollo/client/react" (production) 2.13 KB (0%)
import { useMutation } from "@apollo/client/react" 5.77 KB (0%)
import { useMutation } from "@apollo/client/react" (production) 1.53 KB (0%)
import { useSubscription } from "@apollo/client/react" 6.07 KB (0%)
import { useSubscription } from "@apollo/client/react" (production) 1.83 KB (0%)
import { useSuspenseQuery } from "@apollo/client/react" 7.8 KB (0%)
import { useSuspenseQuery } from "@apollo/client/react" (production) 3.58 KB (0%)
import { useBackgroundQuery } from "@apollo/client/react" 7.62 KB (0%)
import { useBackgroundQuery } from "@apollo/client/react" (production) 3.42 KB (0%)
import { useLoadableQuery } from "@apollo/client/react" 7.61 KB (0%)
import { useLoadableQuery } from "@apollo/client/react" (production) 3.41 KB (0%)
import { useReadQuery } from "@apollo/client/react" 5.85 KB (0%)
import { useReadQuery } from "@apollo/client/react" (production) 1.62 KB (0%)
import { useFragment } from "@apollo/client/react" 5.92 KB (0%)
import { useFragment } from "@apollo/client/react" (production) 1.68 KB (0%)

@jerelmiller jerelmiller merged commit 99d72bf into release-4.0 Apr 17, 2025
47 checks passed
@jerelmiller jerelmiller deleted the jerel/network-error-link branch April 17, 2025 17:38
@github-actions github-actions bot locked as resolved and limited conversation to collaborators May 18, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants