Skip to content

Commit 0ad36d6

Browse files
authored
Merge pull request #22 from guendev/fix-9
fix(core): correct UseFragmentResult type definition
2 parents 1eee82f + d53a206 commit 0ad36d6

File tree

3 files changed

+41
-180
lines changed

3 files changed

+41
-180
lines changed

.changeset/slow-months-camp.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@vue3-apollo/core": patch
3+
"@vue3-apollo/nuxt": patch
4+
---
5+
6+
fix(core): correct UseFragmentResult type definition

packages/core/src/composables/useFragment.ts

Lines changed: 34 additions & 179 deletions
Original file line numberDiff line numberDiff line change
@@ -31,24 +31,25 @@ import { useApolloClient } from './useApolloClient'
3131
*/
3232
export type UseFragmentOptions<TData = unknown, TVariables extends OperationVariables = OperationVariables> = {
3333
/**
34-
* An object containing a `__typename` and primary key fields (such as `id`)
35-
* identifying the entity object from which the fragment will be retrieved,
36-
* or a `{ __ref: "..." }` reference, or a `string` ID (uncommon).
34+
* Entity identifier to read the fragment from.
35+
* Can be an object with __typename and id, a cache reference, or a string ID.
36+
*
37+
* @example
38+
* ```ts
39+
* from: { __typename: 'User', id: '123' }
40+
* from: computed(() => ({ __typename: 'User', id: userId.value }))
41+
* from: 'User:123'
42+
* ```
3743
*/
3844
from: MaybeRefOrGetter<FragmentType<NoInfer<TData>> | Reference | StoreObject | string>
3945

4046
/**
41-
* A GraphQL document created using the `gql` template string tag from
42-
* `graphql-tag` with one or more fragments which will be used to determine
43-
* the shape of data to read. If you provide more than one fragment in this
44-
* document then you must also specify `fragmentName` to select a single.
47+
* GraphQL fragment document.
4548
*/
4649
fragment: DocumentNode | TypedDocumentNode<TData, TVariables>
4750

4851
/**
49-
* The name of the fragment in your GraphQL document to be used. If you do
50-
* not provide a `fragmentName` and there is only one fragment in your
51-
* `fragment` document, then that fragment will be used.
52+
* Fragment name (required if document has multiple fragments).
5253
*/
5354
fragmentName?: string
5455

@@ -61,12 +62,7 @@ export type UseFragmentOptions<TData = unknown, TVariables extends OperationVari
6162
*
6263
* @example
6364
* ```ts
64-
* const shouldWatch = ref(false)
65-
* useFragment({
66-
* fragment: USER_FRAGMENT,
67-
* from: { __typename: 'User', id: '123' },
68-
* enabled: shouldWatch
69-
* })
65+
* enabled: computed(() => isVisible.value)
7066
* ```
7167
*/
7268
enabled?: MaybeRefOrGetter<boolean>
@@ -81,156 +77,46 @@ export type UseFragmentOptions<TData = unknown, TVariables extends OperationVari
8177
prefetch?: boolean
8278

8379
/**
84-
* Any variables that the GraphQL fragment may depend on.
80+
* Fragment variables.
8581
*/
8682
variables?: MaybeRefOrGetter<NoInfer<TVariables>>
8783

8884
/**
8985
* Whether to read from optimistic or non-optimistic cache data.
9086
*
91-
* @defaultValue true
87+
* @default true
9288
*/
9389
optimistic?: boolean
9490
} & UseBaseOption
9591

96-
/**
97-
* Result type for useFragment composable
98-
*/
99-
export type UseFragmentResult<TData, TCompleted extends boolean = boolean> = {
100-
complete: TCompleted
101-
missing?: TCompleted extends true ? never : MissingTree
102-
} & GetDataState<MaybeMasked<TData>, TCompleted extends true ? 'complete' : 'partial'>
92+
/** Fragment result with completion status and data */
93+
export type UseFragmentResult<TData>
94+
= | ({
95+
complete: false
96+
missing?: MissingTree
97+
} & GetDataState<MaybeMasked<TData>, 'partial'>)
98+
| ({
99+
complete: true
100+
missing?: never
101+
} & GetDataState<MaybeMasked<TData>, 'complete'>)
103102

104103
/**
105104
* Composable for reading GraphQL fragments from Apollo Cache with Vue reactivity.
106-
* Provides a lightweight live binding into the Apollo Client Cache and automatically
107-
* updates when the fragment data changes.
105+
* Provides a lightweight live binding and automatically updates when fragment data changes.
108106
*
109107
* @template TData - Type of the fragment result data
110108
* @template TVariables - Type of the fragment variables
111109
*
112-
* @param options - Fragment options including document, from identifier, and other settings
110+
* @param options - Fragment options including document, from identifier, and settings
113111
*
114112
* @returns Object containing fragment state and control methods
115113
*
116-
* @remarks
117-
* **Important Notes:**
118-
* - This composable reads data synchronously from the cache (no network requests)
119-
* - Uses internal Apollo Client APIs (`getFragmentDoc`, `transform`, `maskFragment`)
120-
* - Automatically restarts when `from` or `variables` change
121-
* - Must be called within a Vue component setup or effect scope
122-
*
123-
* **Performance Tips:**
124-
* - Use `computed` for reactive `from` and `variables` instead of `ref` + watchers
125-
* - Set `enabled: false` when fragment is not needed to avoid unnecessary subscriptions
126-
* - Use `shallowRef` when storing fragment data in parent component
127-
* - Fragment reads are cached by Apollo, so multiple reads are efficient
128-
*
129-
* @example
130-
* Basic usage with type narrowing (recommended):
131-
* ```ts
132-
* const userId = ref('123')
133-
* const { result } = useFragment({
134-
* fragment: gql`
135-
* fragment UserFields on User {
136-
* id
137-
* name
138-
* email
139-
* }
140-
* `,
141-
* from: computed(() => ({ __typename: 'User', id: userId.value })),
142-
* fragmentName: 'UserFields'
143-
* })
144-
*
145-
* // TypeScript type narrowing works correctly
146-
* watchEffect(() => {
147-
* if (result.value?.complete) {
148-
* // data is non-optional, missing is never
149-
* console.log(result.value.data.name) // ✅ Type-safe
150-
* }
151-
* })
152-
* ```
153-
*
154114
* @example
155-
* Convenience usage (less type-safe but more ergonomic):
156115
* ```ts
157116
* const { data, complete } = useFragment({
158-
* fragment: USER_FRAGMENT,
117+
* fragment: gql`fragment UserFields on User { id name email }`,
159118
* from: { __typename: 'User', id: '123' }
160119
* })
161-
*
162-
* // Still need optional chaining
163-
* watchEffect(() => {
164-
* if (complete.value) {
165-
* console.log(data.value?.name) // ⚠️ Still optional
166-
* }
167-
* })
168-
* ```
169-
*
170-
* @example
171-
* Handling null/missing data:
172-
* ```ts
173-
* const userId = ref<string | null>(null)
174-
* const { data, complete, missing } = useFragment({
175-
* fragment: USER_FRAGMENT,
176-
* from: computed(() =>
177-
* userId.value ? { __typename: 'User', id: userId.value } : null
178-
* )
179-
* })
180-
*
181-
* // Check if data is available
182-
* watchEffect(() => {
183-
* if (data.value) {
184-
* console.log('User data:', data.value)
185-
* } else if (!complete.value && missing.value) {
186-
* console.warn('Incomplete data:', missing.value)
187-
* }
188-
* })
189-
* ```
190-
*
191-
* @example
192-
* Error handling:
193-
* ```ts
194-
* const { data, error, onError } = useFragment({
195-
* fragment: USER_FRAGMENT,
196-
* from: { __typename: 'User', id: '123' }
197-
* })
198-
*
199-
* onError((err, context) => {
200-
* console.error('Fragment error:', err.message)
201-
* // Handle error (show toast, log to service, etc.)
202-
* })
203-
* ```
204-
*
205-
* @example
206-
* Conditional watching with enabled:
207-
* ```ts
208-
* const isVisible = ref(false)
209-
* const { data, start, stop } = useFragment({
210-
* fragment: USER_FRAGMENT,
211-
* from: { __typename: 'User', id: '123' },
212-
* enabled: isVisible
213-
* })
214-
*
215-
* // Or manually control:
216-
* // enabled: false
217-
* // Then call start() when needed
218-
* ```
219-
*
220-
* @example
221-
* With variables:
222-
* ```ts
223-
* const locale = ref('en')
224-
* const { data } = useFragment({
225-
* fragment: gql`
226-
* fragment UserWithLocale on User {
227-
* id
228-
* name(locale: $locale)
229-
* }
230-
* `,
231-
* from: { __typename: 'User', id: '123' },
232-
* variables: computed(() => ({ locale: locale.value }))
233-
* })
234120
* ```
235121
*/
236122
export function useFragment<TData = unknown, TVariables extends OperationVariables = OperationVariables>(
@@ -436,25 +322,14 @@ export function useFragment<TData = unknown, TVariables extends OperationVariabl
436322

437323
return {
438324
/**
439-
* Whether the fragment data is complete (all fields were found in cache).
440-
*
441-
* **Note: ** For better type narrowing, use `result.value?.complete` instead.
442-
* @see result
325+
* Whether all fragment fields were found in cache.
443326
*/
444327
complete: computed(() => result.value?.complete ?? false),
445328

446329
/**
447330
* The fragment result data.
448331
* Can be undefined if no data has been loaded yet or if `from` is null.
449-
*
450-
* **Note: ** For better type safety, use `result.value` with type narrowing:
451-
* ```ts
452-
* if (result.value?.complete) {
453-
* // result.value.data is non-optional here
454-
* console.log(result.value.data.name)
455-
* }
456-
* ```
457-
* @see result
332+
* For better type safety, use `result` with type narrowing.
458333
*/
459334
data: computed(() => result.value?.data),
460335

@@ -465,9 +340,6 @@ export function useFragment<TData = unknown, TVariables extends OperationVariabl
465340

466341
/**
467342
* A tree of all MissingFieldError messages reported during fragment reading.
468-
*
469-
* **Note: ** For better type narrowing, use `result.value?.missing` instead.
470-
* @see result
471343
*/
472344
missing: computed(() => result.value?.missing),
473345

@@ -485,6 +357,7 @@ export function useFragment<TData = unknown, TVariables extends OperationVariabl
485357

486358
/**
487359
* Event hook that fires when new fragment results are received.
360+
* Only fires when actual data is present (not undefined).
488361
*
489362
* @example
490363
* ```ts
@@ -497,39 +370,20 @@ export function useFragment<TData = unknown, TVariables extends OperationVariabl
497370

498371
/**
499372
* The full fragment result including data, complete status, and missing info.
500-
*
501-
* **Recommended: ** Use this for better TypeScript type narrowing.
373+
* Use this for better TypeScript type narrowing.
502374
*
503375
* @example
504-
* Type-safe access with narrowing:
505376
* ```ts
506-
* const { result } = useFragment<User>(...)
507-
*
508-
* // TypeScript knows data is complete and non-optional
509377
* if (result.value?.complete) {
510-
* console.log(result.value.data.name) // ✅ No optional chaining needed
511-
* // result.value.missing is never
512-
* } else {
513-
* console.log(result.value?.data?.name) // ⚠️ Partial data
514-
* console.log(result.value?.missing) // ✅ Available
515-
* }
516-
* ```
517-
*
518-
* @example
519-
* Convenience access (less type-safe):
520-
* ```ts
521-
* const { data, complete } = useFragment<User>(...)
522-
*
523-
* // Still need optional chaining even when complete is true
524-
* if (complete.value) {
525-
* console.log(data.value?.name) // ⚠️ Still optional
378+
* console.log(result.value.data.name) // No optional chaining needed
526379
* }
527380
* ```
528381
*/
529382
result,
530383

531384
/**
532385
* Manually start watching the fragment.
386+
* Useful after stopping the fragment or when using enabled: false initially.
533387
*
534388
* @example
535389
* ```ts
@@ -546,6 +400,7 @@ export function useFragment<TData = unknown, TVariables extends OperationVariabl
546400

547401
/**
548402
* Stop watching the fragment and unsubscribe from updates.
403+
* Useful for pausing fragments or cleaning up manually.
549404
*
550405
* @example
551406
* ```ts
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
export * from './gql'
1+
export * from "./gql";

0 commit comments

Comments
 (0)