11import 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" ;
211import invariant from "tiny-invariant" ;
3- import { getDocumentId , type GqlResponse } from "./helpers" ;
412import {
5- createSha256 ,
613 errorMessage ,
7- extractOperationName ,
14+ getDocumentId ,
815 getQueryType ,
916 hasPersistedQueryError ,
1017 mergeHeaders ,
18+ type GqlResponse ,
1119} from "./helpers" ;
12- import { print } from "graphql" ;
13- import { isNode } from "graphql/language/ast.js" ;
1420
1521type Options = {
1622 /**
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
1825 * @default false
1926 */
27+ apq ?: boolean ;
28+
29+ /** Deprecated: use `apq: <boolean>` */
2030 persistedQueries ?: boolean ;
2131
2232 /**
@@ -31,6 +41,8 @@ type Options = {
3141 */
3242 defaultHeaders ?: Headers | Record < string , string > ;
3343
44+ mode ?: ModeFlags
45+
3446 /**
3547 * Function to customize creating the documentId from a query
3648 *
@@ -49,98 +61,78 @@ type RequestOptions = {
4961export type ClientFetcher = < TResponse , TVariables > (
5062 astNode : DocumentTypeDecoration < TResponse , TVariables > ,
5163 variables ?: TVariables ,
52- options ?: RequestOptions | AbortSignal , // Backwards compatibility
64+ options ?: RequestOptions ,
5365) => Promise < GqlResponse < TResponse > > ;
5466
5567export const initClientFetcher =
5668 (
5769 endpoint : string ,
5870 {
71+ apq = false ,
5972 persistedQueries = false ,
6073 defaultTimeout = 30000 ,
6174 defaultHeaders = { } ,
62- createDocumentId = < TResult , TVariables > (
63- query : DocumentTypeDecoration < TResult , TVariables > ,
64- ) : string | undefined => getDocumentId ( query ) ,
75+ mode = "document" ,
76+ createDocumentId = getDocumentId ,
6577 } : Options = { } ,
6678 ) : ClientFetcher =>
6779 /**
6880 * Executes a GraphQL query post request on the client.
6981 *
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.
7284 */
7385 async < TResponse , TVariables > (
7486 astNode : DocumentTypeDecoration < TResponse , TVariables > ,
7587 variables ?: TVariables ,
76- optionsOrSignal : RequestOptions | AbortSignal = {
88+ options : RequestOptions = {
7789 signal : AbortSignal . timeout ( defaultTimeout ) ,
78- } satisfies RequestOptions ,
90+ } ,
7991 ) : 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-
8992 // Make sure that we always have a default signal set
9093 if ( ! options . signal ) {
9194 options . signal = AbortSignal . timeout ( defaultTimeout ) ;
9295 }
9396
9497 const query = isNode ( astNode ) ? print ( astNode ) : astNode . toString ( ) ;
95-
96- const operationName = extractOperationName ( query ) ;
9798 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 ) ;
113100
114101 let response : GqlResponse < TResponse > | undefined = undefined ;
115-
116102 const headers = mergeHeaders ( { ...defaultHeaders , ...options . headers } ) ;
117103
104+ const queryType = getQueryType ( query ) ;
105+
106+ apq = apq || persistedQueries ;
107+
118108 // 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 ) ;
127111 response = await parseResponse < GqlResponse < TResponse > > ( ( ) =>
128112 fetch ( url . toString ( ) , {
129- headers : Object . fromEntries ( headers . entries ( ) ) ,
113+ headers : headers ,
130114 method : "GET" ,
131115 credentials : "include" ,
132116 signal : options . signal ,
133117 } ) ,
134118 ) ;
135119 }
136120
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
139131 response = await parseResponse < GqlResponse < TResponse > > ( ( ) =>
140132 fetch ( url . toString ( ) , {
141- headers : Object . fromEntries ( headers . entries ( ) ) ,
133+ headers : headers ,
142134 method : "POST" ,
143- body : JSON . stringify ( { documentId , query , variables , extensions } ) ,
135+ body : createRequestBody ( request ) ,
144136 credentials : "include" ,
145137 signal : options . signal ,
146138 } ) ,
0 commit comments