API SKD (legacy)
TIP
This applies to the legacy monolith API and it's SDK, not the new federated GraphQL API.
The shared @repo/api-sdk package (packages/sdks/api-sdk in the monorepo) builds a single Apollo Client instance used by web, native tooling, and server-side callers that talk to the platform GraphQL API. That client uses an InMemoryCache that is configured for this API on purpose. This page explains what we generate, why it is necessary, and how to keep it in sync when the schema changes.
Why this is not default Apollo behavior
Apollo Client’s default cache normalizes results: it stores objects by __typename + id (or other keyFields) and merges updates from different operations. That works well when every query requests a consistent shape for the same entity.
Our API exposes large, nested trees with different operations selecting different slices of the same logical area (for example professional.men.matches vs professional.match with liveEvents). The schema also uses interfaces and unions heavily (for example LiveEvent and its implementations).
Under default caching, reads and writes can combine into hybrid cache state (partial objects, missing fields on polymorphic lists, or merged subtrees that never came from a single network response). That showed up in practice as wrong or missing values after cache reads, for example in live event timing fields, even though the server response was correct.
So we do not rely on the default merge behavior for this schema. We configure the cache explicitly.
What we configure
possibleTypes
Apollo needs a map of which concrete types implement each interface and which types belong to each union so the cache can resolve fragment matching and polymorphic fields (for example [LiveEvent!] on a match). That data is a pure function of the GraphQL schema.
We generate possibleTypes from the canonical schema file:
apps/api/generated/schema.graphql
so new interface implementations or union members are picked up when codegen runs, without hand-maintaining lists.
Reference: Apollo: Defining possibleTypes manually
typePolicies with keyFields: false on (almost) all composite types
For object and interface types in the schema, we set keyFields: false. That tells Apollo not to give those types a separate normalized cache id; they stay embedded in the parent record as written by each operation, instead of being merged by identity across the store.
This matches the product goal: avoid cross-query identity merges for nested data where different queries are not strict subsets of a single graph of fields.
Reference: Apollo: Disabling normalization
Query field policies: merge: false on every top-level field
The root Query type still has a single stored subtree per client. We set merge: false for every top-level Query field (for example academy, geo, me, professional, …), generated from the schema so new root fields are included automatically when the API adds them.
That means: when a new result writes to Query.professional (or any other top-level field), the entire previous value for that field is replaced by the incoming data, not deep-merged with whatever was there from an earlier, different query. It prevents one operation’s partial tree from being stitched together with another’s.
Reference: Apollo: merge (field policies)
How the file is produced
The configuration is not hand-written. A small Node script in the api-sdk package reads the same SDL as GraphQL Codegen and emits TypeScript:
| Piece | Source |
|---|---|
| Script | packages/sdks/api-sdk/scripts/generate-apollo-cache-config.mjs |
| Input | apps/api/generated/schema.graphql (must exist; produced by the API’s codegen) |
| Output | packages/sdks/api-sdk/src/lib/apollo-cache.generated.ts (exports possibleTypes and typePolicies) |
The @repo/api-sdk pnpm codegen script runs GraphQL Codegen and then this generator so the client documents, types, and Apollo cache config all track the same schema snapshot.
Turborepo task @repo/api-sdk#codegen already depends on @repo/api#codegen, so the schema file is available before the SDK step runs in normal workflows.
When to regenerate
Regenerate the SDK (including apollo-cache.generated.ts) whenever:
- The public GraphQL schema changes in
apps/api(new types, newQueryfields, new interface implementations, new union members). - You see odd cache behavior after a schema change; first confirm codegen has been run and the app is not using a stale build.
Optional: disabling the cache for debugging
createApiSdk({ disableCache: true }) sets Apollo’s default fetchPolicy to no-cache for queries so results bypass the in-memory read path. That is useful to confirm whether a bug is cache-related vs server/transport. It is not a substitute for a correct possibleTypes / typePolicies setup for normal use.
Related reading
- Apollo InMemoryCache configuration
- GraphQL API design – schema conventions (abstract types) (interfaces and unions in the platform schema)