{"id":33063148,"url":"https://github.com/graphql-cascade/graphql-cascade","last_synced_at":"2026-04-13T21:31:46.739Z","repository":{"id":323687892,"uuid":"1094307477","full_name":"graphql-cascade/graphql-cascade","owner":"graphql-cascade","description":"Cascading cache updates for GraphQL - Automatic, zero-boilerplate cache management","archived":false,"fork":false,"pushed_at":"2026-01-10T15:57:25.000Z","size":2410,"stargazers_count":1,"open_issues_count":1,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-01-11T02:39:06.409Z","etag":null,"topics":["apollo-client","cache","graphql","mutations","python","react-query","relay","typescript"],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/graphql-cascade.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-11-11T14:37:39.000Z","updated_at":"2026-01-10T15:57:28.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/graphql-cascade/graphql-cascade","commit_stats":null,"previous_names":["graphql-cascade/graphql-cascade"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/graphql-cascade/graphql-cascade","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/graphql-cascade%2Fgraphql-cascade","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/graphql-cascade%2Fgraphql-cascade/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/graphql-cascade%2Fgraphql-cascade/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/graphql-cascade%2Fgraphql-cascade/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/graphql-cascade","download_url":"https://codeload.github.com/graphql-cascade/graphql-cascade/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/graphql-cascade%2Fgraphql-cascade/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31771813,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-13T20:17:16.280Z","status":"ssl_error","status_checked_at":"2026-04-13T20:17:08.216Z","response_time":93,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["apollo-client","cache","graphql","mutations","python","react-query","relay","typescript"],"created_at":"2025-11-14T06:00:47.907Z","updated_at":"2026-04-13T21:31:46.727Z","avatar_url":"https://github.com/graphql-cascade.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# GraphQL Cascade\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"cascade.png\" alt=\"GraphQL Cascade Logo\" width=\"300\"\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://github.com/graphql-cascade/graphql-cascade/actions/workflows/ci.yml\"\u003e\n    \u003cimg src=\"https://github.com/graphql-cascade/graphql-cascade/actions/workflows/ci.yml/badge.svg\" alt=\"CI Status\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://github.com/graphql-cascade/graphql-cascade/actions/workflows/codeql.yml\"\u003e\n    \u003cimg src=\"https://github.com/graphql-cascade/graphql-cascade/actions/workflows/codeql.yml/badge.svg\" alt=\"CodeQL\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://codecov.io/gh/graphql-cascade/graphql-cascade\"\u003e\n    \u003cimg src=\"https://codecov.io/gh/graphql-cascade/graphql-cascade/branch/main/graph/badge.svg\" alt=\"Coverage\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://www.npmjs.com/package/@graphql-cascade/server\"\u003e\n    \u003cimg src=\"https://badge.fury.io/js/%40graphql-cascade%2Fserver.svg\" alt=\"npm version\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://www.npmjs.com/package/@graphql-cascade/server\"\u003e\n    \u003cimg src=\"https://img.shields.io/npm/dm/@graphql-cascade/server.svg\" alt=\"npm downloads\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://opensource.org/licenses/MIT\"\u003e\n    \u003cimg src=\"https://img.shields.io/badge/License-MIT-yellow.svg\" alt=\"License: MIT\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"./specification/\"\u003e\n    \u003cimg src=\"https://img.shields.io/badge/Specification-v1.1-blue\" alt=\"Specification v1.1\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://github.com/graphql-cascade/graphql-cascade/issues\"\u003e\n    \u003cimg src=\"https://img.shields.io/github/issues/graphql-cascade/graphql-cascade\" alt=\"GitHub issues\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://github.com/graphql-cascade/graphql-cascade/blob/main/CONTRIBUTING.md\"\u003e\n    \u003cimg src=\"https://img.shields.io/badge/PRs-welcome-brightgreen.svg\" alt=\"PRs Welcome\"\u003e\n  \u003c/a\u003e\n\u003c/p\u003e\n\n**Automatic cache consistency for GraphQL** - Servers return all affected entities in mutation responses, eliminating the need for clients to guess which queries to refetch.\n\n## Overview\n\nGraphQL Cascade solves the cache consistency problem by having **servers return all affected entities in mutation responses**. Instead of clients manually invalidating queries and refetching, the server tells you exactly what changed. This makes cache updates automatic, predictable, and impossible to miss.\n\n## Problem\n\nWhen mutations have side effects across multiple entities, clients don't know which queries to refetch.\n\n**Example:** User creates a post\n\n```\nBackend mutation updates:\n  ✓ posts table (new row)\n  ✓ users.postCount (aggregate)\n  ✓ creates notifications (for followers)\n  ✓ affects trending rankings\n  ✓ affects user's timeline\n\nClient currently must guess:\n  \"Do I need to refetch getUserPosts?\"  ← Maybe\n  \"What about getNotifications?\"       ← Maybe\n  \"What about postCount?\"              ← Maybe\n  \"What about trending?\"               ← Maybe\n  → Must refetch multiple queries, guessing what's affected\n  → Easy to miss something and show stale data\n```\n\nWithout Cascade, this forces clients to:\n- **Manually track all side effects** - Error-prone and brittle\n- **Refetch multiple queries** - Slow and wastes bandwidth\n- **Show stale data** - When a refetch is forgotten\n- **Create race conditions** - Multiple mutations conflicting\n\n### Manual Cache Management (Traditional)\n\n\u003cfigure style=\"text-align: center; margin: 2rem 0;\"\u003e\n  \u003cimg\n    src=\"docs/diagrams/manual-cache-management.png\"\n    alt=\"Manual Cache Management Flow - Traditional approach requires manual cache invalidation after mutations\"\n    style=\"max-width: 100%; height: auto; display: block; margin: 0 auto;\"\n  /\u003e\n  \u003cfigcaption style=\"font-size: 0.9em; color: #666; margin-top: 0.5rem;\"\u003eTraditional approach: Client must manually guess and refetch affected queries\u003c/figcaption\u003e\n\u003c/figure\u003e\n\n### Automatic Cache Management (GraphQL Cascade)\n\n\u003cfigure style=\"text-align: center; margin: 2rem 0;\"\u003e\n  \u003cimg\n    src=\"docs/diagrams/automatic-cache-management.png\"\n    alt=\"Automatic Cache Management with GraphQL Cascade - Server returns all affected data in mutation response\"\n    style=\"max-width: 100%; height: auto; display: block; margin: 0 auto;\"\n  /\u003e\n  \u003cfigcaption style=\"font-size: 0.9em; color: #666; margin-top: 0.5rem;\"\u003eGraphQL Cascade: Server returns all affected entities in one response\u003c/figcaption\u003e\n\u003c/figure\u003e\n\n## Solution\n\nGraphQL Cascade solves this by having **servers include all affected entities in the mutation response**. No manual invalidation, no refetching, no guessing.\n\n```\nUser creates post:\n  ↓\nServer mutation executes\n  ↓\nServer discovers what changed:\n  - Post created\n  - User.postCount updated\n  - Notifications created\n  ↓\nServer returns ALL of this in one response\n  ↓\nClient receives everything at once → Cache is complete\n```\n\n### How It Works\n\n**Without Cascade:** Mutation returns only the direct result. Client must guess what else changed.\n\n```graphql\nmutation CreatePost($input: CreatePostInput!) {\n  createPost(input: $input) {\n    post {\n      id\n      title\n      content\n    }\n    # Client has no idea if this affected postCount, notifications, trending posts, etc.\n  }\n}\n```\n\n**With Cascade:** Mutation returns everything that changed, using proper GraphQL unions.\n\n```graphql\nmutation CreatePost($input: CreatePostInput!) {\n  createPost(input: $input) {\n    # The primary mutation result\n    post {\n      id\n      title\n      content\n      authorId\n    }\n\n    # Everything affected by this mutation (in one response!)\n    cascade {\n      updated {\n        __typename\n        # Updated aggregates and relationships\n        ... on User {\n          id\n          postCount          # ← This changed\n          lastPostAt         # ← This changed\n        }\n        # New data created\n        ... on Notification {\n          id\n          message\n          recipientId\n          createdAt\n        }\n      }\n    }\n  }\n}\n```\n\n**The Result:** Client receives everything in one GraphQL response. No refetching, no guessing.\n\n### Before GraphQL Cascade\n```javascript\n// Client must manually track what to refetch\nconst createPost = async (input) =\u003e {\n  const result = await client.mutate({\n    mutation: CREATE_POST,\n    variables: input\n  });\n\n  // Which queries are affected? Developer must know:\n  // - postCount changed (yes)\n  // - notifications changed (yes)\n  // - user.posts changed (yes)\n  // - trending posts changed (maybe)\n  // - followers' timelines changed (probably)\n\n  // Manual refetching required\n  await client.refetchQueries({\n    include: [getUserPosts, getNotifications, getUser]\n    // Easy to miss affected queries → stale data\n  });\n};\n```\n\n### After GraphQL Cascade\n```javascript\n// Server tells client exactly what changed\nconst createPost = async (input) =\u003e {\n  const result = await client.mutate({\n    mutation: CREATE_POST,\n    variables: input\n  });\n\n  // Cascade data is in the response - everything that changed\n  const { cascade } = result.data.createPost;\n\n  // Update cache with all affected entities\n  cascade.updated.forEach(entity =\u003e {\n    client.cache.modify({\n      fields: {\n        [entity.__typename.toLowerCase()](existing) {\n          return entity;  // Update with latest data from server\n        }\n      }\n    });\n  });\n\n  // ✅ No refetching, no guessing, no stale data\n};\n```\n\n### Entity Relationship Tracking\n\nGraphQL Cascade automatically discovers and tracks entity relationships to ensure complete cache updates:\n\n\u003cfigure style=\"text-align: center; margin: 2rem 0;\"\u003e\n  \u003cimg\n    src=\"docs/diagrams/entity-relationships.png\"\n    alt=\"Entity Relationship Tracking - Shows how Cascade tracks relationships between User, Post, Comment, and Notification entities\"\n    style=\"max-width: 100%; height: auto; display: block; margin: 0 auto;\"\n  /\u003e\n  \u003cfigcaption style=\"font-size: 0.9em; color: #666; margin-top: 0.5rem;\"\u003eCascade automatically discovers entity relationships for complete cache invalidation\u003c/figcaption\u003e\n\u003c/figure\u003e\n\n## Quick Start\n\n### Server Implementation\n\nServer implementations vary by framework. Here's a TypeScript/Node.js example with Apollo Server:\n\n```bash\nnpm install @graphql-cascade/server\n```\n\n```typescript\n// schema.ts - Define cascade response types\ntype CascadeEntity = User | Post | Notification;\n\ntype CreatePostPayload {\n  post: Post!\n  cascade: CascadeResponse!\n}\n\ntype CascadeResponse {\n  updated: [CascadeEntity!]!\n  deleted: [CascadeEntity!]\n}\n\n// resolvers.ts - Implement cascade tracking\nimport { createCascadeBuilder } from '@graphql-cascade/server';\n\nconst resolvers = {\n  Mutation: {\n    async createPost(_, { input }, { db }) {\n      // Create post\n      const post = await db.posts.create({\n        title: input.title,\n        content: input.content,\n        authorId: input.authorId\n      });\n\n      // Get affected user\n      const user = await db.users.findById(input.authorId);\n\n      // Build cascade response - server tells client what changed\n      const cascade = createCascadeBuilder();\n\n      // User's postCount changed\n      cascade.addUpdated('User', {\n        id: user.id,\n        postCount: user.posts.length,\n        lastPostAt: new Date()\n      });\n\n      // Create notifications for followers\n      const followers = await db.users.getFollowersOf(input.authorId);\n      followers.forEach(follower =\u003e {\n        cascade.addCreated('Notification', {\n          id: crypto.randomUUID(), // Use your preferred ID strategy (uuid, nanoid, etc.)\n          recipientId: follower.id,\n          message: `${user.name} posted something new`,\n          createdAt: new Date()\n        });\n      });\n\n      return {\n        post,\n        cascade: cascade.build()\n      };\n    }\n  }\n};\n```\n\n### Client (Apollo)\n\n```bash\nnpm install @apollo/client\n```\n\n```typescript\nimport { useMutation, gql, ApolloClient, InMemoryCache } from '@apollo/client';\n\nconst CREATE_POST = gql`\n  mutation CreatePost($input: CreatePostInput!) {\n    createPost(input: $input) {\n      post {\n        id\n        title\n        content\n        authorId\n      }\n      cascade {\n        updated {\n          __typename\n          ... on User {\n            id\n            postCount\n            lastPostAt\n          }\n          ... on Notification {\n            id\n            message\n            recipientId\n            createdAt\n          }\n        }\n      }\n    }\n  }\n`;\n\nfunction CreatePostButton() {\n  const client = useApolloClient(); // Apollo Client from ApolloProvider context\n\n  const [createPost, { loading }] = useMutation(CREATE_POST, {\n    onCompleted: (data) =\u003e {\n      const { cascade } = data.createPost;\n\n      // Server told us what changed - update cache automatically\n      cascade.updated.forEach(entity =\u003e {\n        client.cache.modify({\n          fields: {\n            user(existing = {}, { readField }) {\n              // Update user fields if this is a User entity\n              if (entity.__typename === 'User' \u0026\u0026 entity.id === readField('id', existing)) {\n                return { ...existing, ...entity };\n              }\n              return existing;\n            },\n            notifications(existing = [], { readField }) {\n              // Add new notifications if this is a Notification entity\n              if (entity.__typename === 'Notification') {\n                return [...existing, entity];\n              }\n              return existing;\n            }\n          }\n        });\n      });\n    }\n  });\n\n  const handleClick = async () =\u003e {\n    await createPost({\n      variables: {\n        input: {\n          title: 'New Post',\n          content: 'Hello world',\n          authorId: 'user-123'\n        }\n      }\n    });\n  };\n\n  return \u003cbutton onClick={handleClick} disabled={loading}\u003eCreate Post\u003c/button\u003e;\n}\n```\n\n### Client (React Query)\n\n```bash\nnpm install @tanstack/react-query\n```\n\n```typescript\nimport { useMutation, useQueryClient } from '@tanstack/react-query';\nimport { GraphQLClient, gql } from 'graphql-request';\n\n// Initialize GraphQL client\nconst graphqlClient = new GraphQLClient('http://localhost:4000/graphql');\n\nconst CREATE_POST = gql`\n  mutation CreatePost($input: CreatePostInput!) {\n    createPost(input: $input) {\n      post { id, title, content, authorId }\n      cascade {\n        updated {\n          __typename\n          ... on User { id, postCount, lastPostAt }\n          ... on Notification { id, message, recipientId, createdAt }\n        }\n      }\n    }\n  }\n`;\n\nfunction CreatePostButton() {\n  const queryClient = useQueryClient();\n\n  const mutation = useMutation({\n    mutationFn: async (input) =\u003e {\n      const response = await graphqlClient.request(CREATE_POST, { input });\n      return response.createPost;\n    },\n    onSuccess: (data) =\u003e {\n      const { cascade } = data;\n\n      // Server told us exactly what changed\n      cascade.updated.forEach(entity =\u003e {\n        if (entity.__typename === 'User') {\n          // Invalidate and refetch user-related queries\n          queryClient.setQueryData(['user', entity.id], entity);\n        } else if (entity.__typename === 'Notification') {\n          // Update notifications cache\n          queryClient.setQueryData(['notifications'], (old = []) =\u003e [...old, entity]);\n        }\n      });\n    }\n  });\n\n  return (\n    \u003cbutton\n      onClick={() =\u003e mutation.mutate({\n        title: 'New Post',\n        content: 'Hello world',\n        authorId: 'user-123'\n      })}\n      disabled={mutation.isPending}\n    \u003e\n      Create Post\n    \u003c/button\u003e\n  );\n}\n```\n\n## TypeScript Code Generation\n\nGet full type safety with automatic TypeScript code generation:\n\n```bash\n# Initialize codegen configuration\nnpx cascade codegen init\n\n# Install dependencies\nnpm install -D @graphql-codegen/cli @graphql-codegen/typescript @graphql-codegen/typescript-operations @graphql-cascade/codegen\n\n# Generate types\nnpx cascade codegen\n```\n\n**Before codegen:**\n```typescript\nconst [createTodo] = useCascadeMutation(CREATE_TODO);\n//    ^ any type, no autocomplete\n```\n\n**After codegen:**\n```typescript\nimport { CreateTodoDocument, CreateTodoMutation } from './generated/graphql';\n\nconst [createTodo] = useCascadeMutation\u003cCreateTodoMutation\u003e(CreateTodoDocument);\n//    ^ Fully typed with IDE autocomplete for cascade updates!\n\nconst result = await createTodo({ variables: { title: 'New Todo' } });\nresult.cascade.updated.forEach(entity =\u003e {\n  // Full type safety on cascade data\n  console.log(`Updated ${entity.__typename}`);\n});\n```\n\nThe codegen plugin automatically generates:\n- Type-safe mutation and query types\n- Cascade helper types (`CascadeOf\u003cT\u003e`, `DataOf\u003cT\u003e`)\n- Union type guards for error handling\n- Pre-configured `UnionCascadeConfig` objects\n\nSee [@graphql-cascade/codegen](./packages/codegen) for full documentation.\n\n## Packages\n\n| Package | Description |\n|---------|-------------|\n| [@graphql-cascade/server](./packages/server-node) | Server implementation for Node.js/TypeScript |\n| [@graphql-cascade/client-apollo](./packages/client-apollo) | Apollo Client integration |\n| [@graphql-cascade/client-react-query](./packages/client-react-query) | React Query integration |\n| [@graphql-cascade/client-relay](./packages/client-relay) | Relay Modern integration |\n| [@graphql-cascade/client-urql](./packages/client-urql) | URQL integration |\n| [@graphql-cascade/codegen](./packages/codegen) | TypeScript code generation plugin |\n| [@graphql-cascade/cli](./packages/cli) | CLI tools for development and debugging |\n| [@graphql-cascade/conformance](./packages/conformance) | Conformance test suite |\n\n## Documentation\n\n- **[Guide](./docs/guide/)** - Getting started and core concepts\n- **[Server Documentation](./docs/server/)** - Server implementation guides\n- **[Client Documentation](./docs/clients/)** - Client library guides\n- **[CLI Documentation](./docs/cli/)** - Command-line tools\n- **[Specification](./docs/specification/)** - Technical specification\n- **[API Reference](./docs/api/)** - Complete API documentation\n\n## Community\n\n- **GitHub Discussions**: Ask questions and share ideas\n- **Contributing**: See our [contribution guide](./CONTRIBUTING.md)\n\n## Status\n\n- ✅ Core specification complete\n- ✅ TypeScript server implementation\n- ✅ Apollo Client integration\n- ✅ React Query integration\n- ✅ Relay integration\n- ✅ URQL integration\n- ✅ CLI tools\n- ✅ Conformance test suite\n\n## License\n\nMIT License - see [LICENSE](./LICENSE) for details.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgraphql-cascade%2Fgraphql-cascade","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgraphql-cascade%2Fgraphql-cascade","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgraphql-cascade%2Fgraphql-cascade/lists"}