Skip to content

Commit d164989

Browse files
committed
docs: add all core concepts
1 parent 25aefb1 commit d164989

File tree

11 files changed

+4117
-117
lines changed

11 files changed

+4117
-117
lines changed

packages/docs/.vitepress/config.ts

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ const guideSidebar = [
2020
{ text: 'Subscriptions', link: '/data/subscriptions' },
2121
{ text: 'Fragments', link: '/data/fragments' },
2222
{ text: 'Data Masking', link: '/data/data-masking' },
23+
{ text: 'Suspense', link: '/data/suspense' },
2324
{ text: 'Error Handling', link: '/data/error-handling' },
2425
{ text: 'TypeScript', link: '/data/typescript' },
2526
],
@@ -63,6 +64,7 @@ const guideSidebar = [
6364
items: [
6465
{ text: 'Basic HTTP', link: '/networking/basic-http' },
6566
{ text: 'Authentication', link: '/networking/authentication' },
67+
{ text: 'SSE', link: '/networking/sse' },
6668
{ text: 'WebSocket', link: '/networking/websocket' },
6769
],
6870
},
@@ -80,14 +82,7 @@ export default defineConfig({
8082
description: 'Apollo/GraphQL integration for VueJS',
8183
markdown: {
8284
codeTransformers: [
83-
transformerTwoslash({
84-
twoslashOptions: {
85-
extraFiles: {
86-
'shims.d.ts': `
87-
`,
88-
},
89-
},
90-
}) as any,
85+
transformerTwoslash() as any,
9186
],
9287
},
9388
head: [['link', { rel: 'icon', href: '/favicon.png' }]],
@@ -150,4 +145,4 @@ export default defineConfig({
150145
noExternal: ['vue-github-button'],
151146
},
152147
},
153-
})
148+
})

packages/docs/data/data-masking.md

Lines changed: 220 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,223 @@
11
# Data Masking
22

3-
::: warning Work in Progress
4-
This page is under construction.
3+
Data masking prevents components from accessing GraphQL fields they didn't explicitly request. This creates loosely coupled components that are more resistant to breaking changes.
4+
5+
::: tip Recommended: Use with GraphQL Codegen
6+
Data masking works best with [GraphQL Codegen](https://the-guild.dev/graphql/codegen) for type-safe masked types. See the [TypeScript page](/data/typescript) for setup instructions.
7+
:::
8+
9+
## The Problem
10+
11+
Consider a parent component that fetches posts and a child component that displays post details:
12+
13+
::: code-group
14+
15+
```ts twoslash [Posts.vue (setup)]
16+
import type { TypedDocumentNode } from '@apollo/client'
17+
import { useQuery } from '@vue/apollo-composable'
18+
19+
declare const POST_DETAILS_FRAGMENT: TypedDocumentNode<{
20+
title: string
21+
publishedAt: string
22+
}>
23+
declare const gql: (literals: TemplateStringsArray, ...placeholders: any[]) => TypedDocumentNode<{
24+
posts: Array<{ id: string, title: string, publishedAt: string }>
25+
}>
26+
27+
// ---cut---
28+
const GET_POSTS = gql`
29+
query GetPosts {
30+
posts {
31+
id
32+
...PostDetailsFragment
33+
}
34+
}
35+
${POST_DETAILS_FRAGMENT}
36+
`
37+
38+
const { current } = useQuery(GET_POSTS)
39+
40+
// Filter by publishedAt (defined in PostDetailsFragment)
41+
const published = current.value.result?.posts.filter(post => post.publishedAt)
42+
```
43+
44+
```ts twoslash [PostDetails.vue (setup)]
45+
import type { TypedDocumentNode } from '@apollo/client'
46+
47+
declare const gql: (literals: TemplateStringsArray, ...placeholders: any[]) => TypedDocumentNode<{
48+
title: string
49+
publishedAt: string
50+
}>
51+
52+
// ---cut---
53+
export const POST_DETAILS_FRAGMENT = gql`
54+
fragment PostDetailsFragment on Post {
55+
title
56+
publishedAt
57+
}
58+
`
59+
```
60+
61+
:::
62+
63+
If `PostDetails` removes `publishedAt` from its fragment (because it no longer displays it), the parent component breaks silently. This **implicit dependency** between components becomes harder to track as applications grow.
64+
65+
## Enabling Data Masking
66+
67+
Enable data masking in the Apollo Client constructor:
68+
69+
```ts twoslash {6}
70+
import { ApolloClient, HttpLink, InMemoryCache } from '@apollo/client'
71+
72+
const client = new ApolloClient({
73+
link: new HttpLink({ uri: 'https://api.example.com/graphql' }),
74+
cache: new InMemoryCache(),
75+
dataMasking: true, // Enable data masking
76+
})
77+
```
78+
79+
With data masking enabled, fields defined in fragments are hidden from components that don't own them. The parent component can only access fields it explicitly requests.
80+
81+
## Reading Masked Data
82+
83+
Use [`useFragment`](/api/composable/functions/useFragment) to read masked fragment data in components:
84+
85+
```vue twoslash
86+
<script setup lang="ts">
87+
import { useFragment } from '@vue/apollo-composable'
88+
// ---cut-start---
89+
// eslint-disable-next-line perfectionist/sort-imports
90+
import type { TypedDocumentNode } from '@apollo/client'
91+
92+
declare const POST_DETAILS_FRAGMENT: TypedDocumentNode<{
93+
title: string
94+
publishedAt: string
95+
}>
96+
// ---cut-end---
97+
98+
const {
99+
post
100+
} = defineProps<{
101+
post: { __typename: 'Post', id: string }
102+
}>()
103+
104+
const { current } = useFragment({
105+
fragment: POST_DETAILS_FRAGMENT,
106+
from: () => post,
107+
})
108+
</script>
109+
110+
<template>
111+
<div v-if="current.resultState === 'complete'">
112+
<h2>{{ current.result.title }}</h2>
113+
<p>{{ current.result.publishedAt }}</p>
114+
</div>
115+
</template>
116+
```
117+
118+
The `data` from `useFragment` contains only the fields defined in the fragment—not fields from parent queries or sibling fragments.
119+
120+
## Fixing the Parent Component
121+
122+
With data masking, the parent component must explicitly request any fields it needs:
123+
124+
```ts
125+
const GET_POSTS = gql`
126+
query GetPosts {
127+
posts {
128+
id
129+
publishedAt # Now explicit - won't break if fragment changes
130+
...PostDetailsFragment
131+
}
132+
}
133+
${POST_DETAILS_FRAGMENT}
134+
`
135+
```
136+
137+
Now if `PostDetails` removes `publishedAt` from its fragment, the parent query still works because it requests the field directly.
138+
139+
## The `@unmask` Directive
140+
141+
Use `@unmask` to selectively disable masking for specific fragments:
142+
143+
```graphql
144+
query GetPosts {
145+
posts {
146+
id
147+
...PostDetailsFragment @unmask
148+
}
149+
}
150+
```
151+
152+
::: warning Use sparingly
153+
The `@unmask` directive is an escape hatch. Prefer adding needed fields to the parent query explicitly. `@unmask` is primarily useful during [incremental adoption](#incremental-adoption).
5154
:::
155+
156+
### Migrate Mode
157+
158+
During migration, use `@unmask(mode: "migrate")` to get development warnings when accessing would-be masked fields:
159+
160+
```graphql
161+
query GetPosts {
162+
posts {
163+
id
164+
...PostDetailsFragment @unmask(mode: "migrate")
165+
}
166+
}
167+
```
168+
169+
This logs warnings in development when you access fields that would be masked, helping you identify implicit dependencies.
170+
171+
## What Gets Masked
172+
173+
Data masking applies to all operation types that read data:
174+
175+
| API | Masked |
176+
|-----|--------|
177+
| `useQuery` result | Yes |
178+
| `useMutation` result | Yes |
179+
| `useSubscription` result | Yes |
180+
| `useMutation` `update` callback | No |
181+
| `useMutation` `refetchQueries` callback | No |
182+
| `subscribeToMore` `updateQuery` callback | No |
183+
| Cache APIs (`readQuery`, `readFragment`) | No |
184+
185+
## Incremental Adoption
186+
187+
For existing applications, adopt data masking incrementally:
188+
189+
### 1. Add `@unmask` to All Fragments
190+
191+
Before enabling data masking, add `@unmask(mode: "migrate")` to all fragment spreads to prevent breaking changes:
192+
193+
```graphql
194+
query GetPosts {
195+
posts {
196+
id
197+
...PostDetailsFragment @unmask(mode: "migrate")
198+
}
199+
}
200+
```
201+
202+
### 2. Enable Data Masking
203+
204+
```ts
205+
const client = new ApolloClient({
206+
dataMasking: true,
207+
// ...
208+
})
209+
```
210+
211+
### 3. Refactor Components
212+
213+
Gradually refactor components to use `useFragment` and remove `@unmask` directives:
214+
215+
1. Look for console warnings about accessing masked fields
216+
2. Update components to use `useFragment` for their fragment data
217+
3. Add any needed fields explicitly to parent queries
218+
4. Remove `@unmask` when no warnings remain
219+
220+
## Next Steps
221+
222+
- [Fragments](/data/fragments) - Learn about GraphQL fragments and `useFragment`
223+
- [TypeScript](/data/typescript) - Set up GraphQL Codegen for type-safe data masking

0 commit comments

Comments
 (0)