AgentSkillsCN

apollo-client

当您使用Apollo Client 3.10编写或修改GraphQL操作、钩子或突变时,可选用此技能。它严格遵循乐观响应、缓存更新以及TypeScript类型生成的最佳实践。当您需要创建新查询/突变、审查Apollo代码,或排查缓存问题时,可选用此技能。

SKILL.md
--- frontmatter
name: apollo-client
description: This skill should be used when writing or modifying GraphQL operations, hooks, or mutations using Apollo Client 3.10. It enforces best practices for optimistic responses, cache updates, and TypeScript type generation. Use this skill when creating new queries/mutations, reviewing Apollo code, or troubleshooting cache issues.

Apollo Client 3.10

Overview

This skill provides best practices for Apollo Client 3.10 in this codebase, ensuring consistent patterns for GraphQL operations, optimistic UI updates, cache management, and TypeScript type safety.

Quick Reference

Generator Commands

After modifying any operations.graphql file, run the appropriate generator:

bash
bun run generate:types:dev        # Development environment
bun run generate:types:staging    # Staging environment
bun run generate:types:production # Production environment

Note: Replace bun with your project's package manager (npm, yarn, pnpm) as needed.

Import Pattern

All GraphQL types, hooks, and documents must come from generated types:

typescript
import {
  useGetPlayerQuery,
  useUpdatePlayerMutation,
  GetPlayerQuery,
  PlayerFragment,
  ListPlayersDocument,
} from "@/generated/graphql";

Core Rules

1. Only Use Generated Types

Import all GraphQL-related types from @/generated/graphql. Never define manual TypeScript types for GraphQL entities.

typescript
// CORRECT
import { PlayerFragment, useUpdatePlayerMutation } from "@/generated/graphql";

// INCORRECT - Never do this
type Player = { id: string; name: string };

2. Never Modify Generated Files

The generated/graphql.ts file is auto-generated by codegen. To change types:

  1. Edit the appropriate operations.graphql file in the feature directory
  2. Run bun run generate:types:dev
  3. Import the newly generated types

3. Always Run Generator After Schema Changes

After modifying any operations.graphql file, immediately run the generator before committing. Verify changes compile with bun run typecheck.

4. Always Include Optimistic Response

Every mutation must include an optimisticResponse for instant UI feedback:

typescript
const [updatePlayer] = useUpdatePlayerMutation({
  optimisticResponse: variables => ({
    __typename: "Mutation",
    updatePlayer: {
      __typename: "Player",
      id: variables.id,
      name: variables.input.name,
      updatedAt: new Date().toISOString(),
    },
  }),
});

Key requirements:

  • Always include __typename for every object in the response
  • Always include id for cache normalization
  • Use temporary IDs for new objects (e.g., crypto.randomUUID())
  • Include all fields that the mutation returns

5. Always Update Cache

Every mutation must handle cache updates using one of these strategies:

Automatic Updates - When mutation returns the full entity with id and __typename, Apollo updates automatically. No extra code needed.

cache.modify - For adding/removing items from lists:

typescript
const [addPlayer] = useAddPlayerMutation({
  optimisticResponse: {
    /* ... */
  },
  update(cache, { data }) {
    cache.modify({
      fields: {
        players(existingPlayers = [], { readField }) {
          const newRef = cache.writeFragment({
            data: data.addPlayer,
            fragment: PlayerFragmentDoc,
          });
          return [...existingPlayers, newRef];
        },
      },
    });
  },
});

refetchQueries - Fallback for complex scenarios:

typescript
const [complexMutation] = useComplexMutation({
  refetchQueries: ["ListPlayers"],
  awaitRefetchQueries: true,
});

Operations.graphql Structure

Fragment-First Pattern

Define fragments before queries/mutations that use them:

graphql
# 1. Fragments first
fragment PlayerFragment on Player {
  id
  knownName
  firstName
  lastName
  team {
    id
    name
  }
}

# 2. Queries second
query GetPlayer($id: ID!) {
  player(id: $id) {
    ...PlayerFragment
  }
}

# 3. Mutations last
mutation UpdatePlayer($id: ID!, $input: UpdatePlayerInput!) {
  updatePlayer(id: $id, input: $input) {
    ...PlayerFragment
  }
}

Include Required Fields

Mutations must return all fields needed for cache updates:

graphql
mutation AddPlayerToKanban($input: AddPlayerToKanbanInput!) {
  addPlayerToKanban(input: $input) {
    id # Required for cache normalization
    position
    notes
    kanbanPhaseId
    kanbanPhase {
      # Include related objects
      id
      name
    }
    createdAt
    updatedAt
  }
}

Query Best Practices

Fetch Policies

typescript
// Frequently changing data - balance speed and freshness
fetchPolicy: "cache-and-network";

// Stable reference data - prioritize cache
fetchPolicy: "cache-first";

// Always-fresh data - skip cache
fetchPolicy: "network-only";

Skip When Variables Undefined

typescript
const { data } = useGetPlayerQuery({
  variables: { id: playerId! },
  skip: !playerId,
});

Error Handling

Use onError callback instead of try/catch with console.log:

typescript
const [mutation] = useMutation(MUTATION, {
  onError: error => {
    setErrorState("Failed to update. Please try again.");
  },
});

Complete Mutation Pattern

Reference references/mutation-patterns.md for comprehensive examples of the complete mutation pattern including optimistic responses, cache updates, and error handling.

Validation Checklist

When writing or reviewing Apollo code, verify:

  • All types imported from @/generated/graphql
  • No manual type definitions for GraphQL entities
  • Every mutation has optimisticResponse
  • Every mutation has cache update strategy
  • __typename included in all optimistic response objects
  • id included in all optimistic response objects
  • Queries use appropriate fetchPolicy
  • Queries use skip when variables may be undefined
  • Error handling via onError callback
  • Generator was run after operations.graphql changes