1
1
import type { DocumentTypeDecoration } from "@graphql-typed-document-node/core" ;
2
+ import { print } from "graphql" ;
3
+ import { isNode } from "graphql/language/ast.js" ;
4
+ import {
5
+ createRequest ,
6
+ createRequestBody ,
7
+ createRequestURL ,
8
+ isPersistedQuery ,
9
+ type ModeFlags ,
10
+ } from "request" ;
2
11
import invariant from "tiny-invariant" ;
3
- import { getDocumentId , type GqlResponse } from "./helpers" ;
4
12
import {
5
- createSha256 ,
6
13
errorMessage ,
7
- extractOperationName ,
14
+ getDocumentId ,
8
15
getQueryType ,
9
16
hasPersistedQueryError ,
10
17
mergeHeaders ,
18
+ type GqlResponse ,
11
19
} from "./helpers" ;
12
- import { print } from "graphql" ;
13
- import { isNode } from "graphql/language/ast.js" ;
14
20
15
21
type Options = {
16
22
/**
17
- * Enable use of persisted queries, this will always add a extra roundtrip to the server if queries aren't cacheable
23
+ * Enable use of automated persisted queries, this will always add a extra
24
+ * roundtrip to the server if queries aren't cacheable
18
25
* @default false
19
26
*/
27
+ apq ?: boolean ;
28
+
29
+ /** Deprecated: use `apq: <boolean>` */
20
30
persistedQueries ?: boolean ;
21
31
22
32
/**
@@ -31,6 +41,8 @@ type Options = {
31
41
*/
32
42
defaultHeaders ?: Headers | Record < string , string > ;
33
43
44
+ mode ?: ModeFlags
45
+
34
46
/**
35
47
* Function to customize creating the documentId from a query
36
48
*
@@ -49,98 +61,78 @@ type RequestOptions = {
49
61
export type ClientFetcher = < TResponse , TVariables > (
50
62
astNode : DocumentTypeDecoration < TResponse , TVariables > ,
51
63
variables ?: TVariables ,
52
- options ?: RequestOptions | AbortSignal , // Backwards compatibility
64
+ options ?: RequestOptions ,
53
65
) => Promise < GqlResponse < TResponse > > ;
54
66
55
67
export const initClientFetcher =
56
68
(
57
69
endpoint : string ,
58
70
{
71
+ apq = false ,
59
72
persistedQueries = false ,
60
73
defaultTimeout = 30000 ,
61
74
defaultHeaders = { } ,
62
- createDocumentId = < TResult , TVariables > (
63
- query : DocumentTypeDecoration < TResult , TVariables > ,
64
- ) : string | undefined => getDocumentId ( query ) ,
75
+ mode = "document" ,
76
+ createDocumentId = getDocumentId ,
65
77
} : Options = { } ,
66
78
) : ClientFetcher =>
67
79
/**
68
80
* Executes a GraphQL query post request on the client.
69
81
*
70
- * This is the only fetcher that uses user information in the call since all user information is only
71
- * used after rendering the page for caching reasons.
82
+ * This is the only fetcher that uses user information in the call since all
83
+ * user information is only used after rendering the page for caching reasons.
72
84
*/
73
85
async < TResponse , TVariables > (
74
86
astNode : DocumentTypeDecoration < TResponse , TVariables > ,
75
87
variables ?: TVariables ,
76
- optionsOrSignal : RequestOptions | AbortSignal = {
88
+ options : RequestOptions = {
77
89
signal : AbortSignal . timeout ( defaultTimeout ) ,
78
- } satisfies RequestOptions ,
90
+ } ,
79
91
) : Promise < GqlResponse < TResponse > > => {
80
- // For backwards compatibility, when options is an AbortSignal we transform
81
- // it into a RequestOptions object
82
- const options : RequestOptions = { } ;
83
- if ( optionsOrSignal instanceof AbortSignal ) {
84
- options . signal = optionsOrSignal ;
85
- } else {
86
- Object . assign ( options , optionsOrSignal ) ;
87
- }
88
-
89
92
// Make sure that we always have a default signal set
90
93
if ( ! options . signal ) {
91
94
options . signal = AbortSignal . timeout ( defaultTimeout ) ;
92
95
}
93
96
94
97
const query = isNode ( astNode ) ? print ( astNode ) : astNode . toString ( ) ;
95
-
96
- const operationName = extractOperationName ( query ) ;
97
98
const documentId = createDocumentId ( astNode ) ;
98
-
99
- let extensions = { } ;
100
- if ( persistedQueries ) {
101
- const hash = await createSha256 ( query ) ;
102
-
103
- extensions = {
104
- persistedQuery : {
105
- version : 1 ,
106
- sha256Hash : hash ,
107
- } ,
108
- } ;
109
- }
110
-
111
- const url = new URL ( endpoint ) ;
112
- url . searchParams . set ( "op" , operationName ?? "" ) ;
99
+ const request = await createRequest ( mode , query , variables , documentId ) ;
113
100
114
101
let response : GqlResponse < TResponse > | undefined = undefined ;
115
-
116
102
const headers = mergeHeaders ( { ...defaultHeaders , ...options . headers } ) ;
117
103
104
+ const queryType = getQueryType ( query ) ;
105
+
106
+ apq = apq || persistedQueries ;
107
+
118
108
// For queries we can use GET requests if persisted queries are enabled
119
- if ( persistedQueries && getQueryType ( query ) === "query" ) {
120
- url . searchParams . set ( "extensions" , JSON . stringify ( extensions ) ) ;
121
- if ( variables ) {
122
- url . searchParams . set ( "variables" , JSON . stringify ( variables ) ) ;
123
- }
124
- if ( documentId ) {
125
- url . searchParams . set ( "documentId" , documentId ) ;
126
- }
109
+ if ( queryType === "query" && ( apq || isPersistedQuery ( request ) ) ) {
110
+ const url = createRequestURL ( endpoint , request ) ;
127
111
response = await parseResponse < GqlResponse < TResponse > > ( ( ) =>
128
112
fetch ( url . toString ( ) , {
129
- headers : Object . fromEntries ( headers . entries ( ) ) ,
113
+ headers : headers ,
130
114
method : "GET" ,
131
115
credentials : "include" ,
132
116
signal : options . signal ,
133
117
} ) ,
134
118
) ;
135
119
}
136
120
137
- if ( ! response || hasPersistedQueryError ( response ) ) {
138
- // Persisted query not used or found, fall back to POST request and include extension to cache the query on the server
121
+ // For failed APQ calls or mutations we need to fall back to POST requests
122
+ if (
123
+ ! response ||
124
+ ( ! isPersistedQuery ( request ) && hasPersistedQueryError ( response ) )
125
+ ) {
126
+ const url = new URL ( endpoint ) ;
127
+ url . searchParams . append ( "op" , request . operationName ) ;
128
+
129
+ // Persisted query not used or found, fall back to POST request and
130
+ // include extension to cache the query on the server
139
131
response = await parseResponse < GqlResponse < TResponse > > ( ( ) =>
140
132
fetch ( url . toString ( ) , {
141
- headers : Object . fromEntries ( headers . entries ( ) ) ,
133
+ headers : headers ,
142
134
method : "POST" ,
143
- body : JSON . stringify ( { documentId , query , variables , extensions } ) ,
135
+ body : createRequestBody ( request ) ,
144
136
credentials : "include" ,
145
137
signal : options . signal ,
146
138
} ) ,
0 commit comments