graphql-fetch.ts 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314
  1. import { cookies } from 'next/headers'
  2. import { type DocumentNode } from "graphql";
  3. import {
  4. type OperationVariables,
  5. ApolloClient
  6. } from "@apollo/client";
  7. import {GraphqlRequestResult} from "@/types/graphqlFetch/type";
  8. import {getClient} from "@/lib/ApolloClientServer";
  9. import {IS_GUEST,GUEST_CART_TOKEN} from "@/utils/constants";
  10. import { decodeJWT } from "@/utils/jwt-cookie";
  11. /* 定义自己的context类型
  12. import "@apollo/client";
  13. import { HttpLink } from "@apollo/client";
  14. declare module "@apollo/client" {
  15. interface DefaultContext extends HttpLink.ContextOptions {}
  16. }
  17. */
  18. // Comprehensive error handling example. https://www.apollographql.com/docs/react/data/error-handling
  19. export type CacheLifePreset =
  20. | "seconds"
  21. | "minutes"
  22. | "hours"
  23. | "days"
  24. | "weeks"
  25. | "max";
  26. export type CacheLifeOption = number | CacheLifePreset;
  27. export function getRevalidateTime(
  28. life?: CacheLifeOption
  29. ): number | false {
  30. if (!life) return false;
  31. if (typeof life === "number") return life;
  32. switch (life) {
  33. case "seconds":
  34. return 10;
  35. case "minutes":
  36. return 60;
  37. case "hours":
  38. return 3600;
  39. case "days":
  40. return 86400;
  41. case "weeks":
  42. return 604800;
  43. case "max":
  44. return false;
  45. default:
  46. return false;
  47. }
  48. }
  49. export function stableStringify(value: unknown): string {
  50. if (value === null || typeof value !== "object") {
  51. return JSON.stringify(value);
  52. }
  53. if (Array.isArray(value)) {
  54. return `[${value.map(stableStringify).join(",")}]`;
  55. }
  56. const obj = value as Record<string, unknown>;
  57. return `{${Object.keys(obj)
  58. .sort()
  59. .map(
  60. (key) => `"${key}":${stableStringify(obj[key])}`
  61. )
  62. .join(",")}}`;
  63. }
  64. export interface GraphQLRequestOptions {
  65. tags?: string[];
  66. life?: CacheLifeOption;
  67. noCache?: boolean;
  68. context?: Record<string, unknown>;
  69. fetchPolicy?:
  70. | "cache-first"
  71. | "network-only"
  72. | "no-cache"
  73. | "cache-only";
  74. }
  75. export async function graphqlRequest<
  76. TData = unknown,
  77. TVariables extends OperationVariables = OperationVariables
  78. >(
  79. query: DocumentNode,
  80. variables?: TVariables,
  81. options?: GraphQLRequestOptions
  82. ): Promise<GraphqlRequestResult<TData>> {
  83. const client = getClient();
  84. let resData;
  85. const revalidate = getRevalidateTime(options?.life);
  86. let queryOption: ApolloClient.QueryOptions<TData> = {
  87. query,
  88. variables,
  89. fetchPolicy: "network-only",
  90. context: {
  91. fetchOptions: {
  92. next: {
  93. revalidate,
  94. tags: options?.tags,
  95. },
  96. },
  97. }
  98. };
  99. if (options?.noCache) {
  100. /***
  101. * client.query的参数是一个对象:
  102. * {query, variables, context, fetchPolicy, errorPolicy}
  103. * context.fetchOptions 可以设置nextjs fetch的缓存策略 https://www.apollographql.com/docs/react/integrations/nextjs
  104. * context: {
  105. fetchOptions: {
  106. next: {
  107. revalidate: 60, // 对应 life: 'minutes'
  108. tags: ['posts'], // 对应 tags 选项
  109. },
  110. },
  111. },
  112. */
  113. queryOption = {
  114. query,
  115. variables,
  116. context: options?.context,
  117. fetchPolicy: "no-cache", // 跳过 apollo client 的缓存,直接调fetch
  118. };
  119. }
  120. if (options?.context) {
  121. throw new Error(
  122. "graphqlRequest: Caching with `context` is unsafe. Use noCache instead."
  123. );
  124. }
  125. try {
  126. // Promise-based APIs (e.g. client.query, client.mutate) - Errors either reject the promise or are returned in the result as the error field.
  127. // 如果错误被reject 则会进入catch
  128. const result: ApolloClient.QueryResult<TData> = await client.query(queryOption);
  129. resData = result.data || null;
  130. return {data: resData, error: result.error? result.error.message : '' };
  131. } catch (error) {
  132. throw error;
  133. }
  134. }
  135. export async function graphqlRequestNoCache<
  136. TData = unknown,
  137. TVariables extends OperationVariables = OperationVariables
  138. >(
  139. query: DocumentNode,
  140. variables?: TVariables,
  141. options?: Omit<
  142. GraphQLRequestOptions,
  143. "noCache" | "tags" | "life"
  144. >
  145. ): Promise<GraphqlRequestResult<TData>> {
  146. return graphqlRequest<TData, TVariables>(
  147. query,
  148. variables,
  149. {
  150. ...options,
  151. noCache: true,
  152. }
  153. );
  154. }
  155. export async function authorizationGraphqlMutate<
  156. TData = unknown,
  157. TVariables extends OperationVariables = OperationVariables
  158. >(
  159. mutation: DocumentNode,
  160. variables?: TVariables,
  161. operationName?: string
  162. ): Promise<GraphqlRequestResult<TData>> {
  163. const cookieStore = await cookies();
  164. const isGuest = cookieStore.get(IS_GUEST);
  165. const authToken = cookieStore.get(GUEST_CART_TOKEN);
  166. let token = '';
  167. if(!authToken) {
  168. return {data: null, error: 'Authorization token not found!'};
  169. }
  170. if(isGuest?.value === 'false') { // 登录用户
  171. token = authToken.value;
  172. } else {
  173. // 游客
  174. const jwtRes = decodeJWT<{
  175. sessionToken: string;
  176. cartId: number;
  177. isGuest: boolean;
  178. }>(authToken.value, true);
  179. token = jwtRes?.sessionToken || '';
  180. }
  181. const client = getClient();
  182. try {
  183. const mutateOption: ApolloClient.MutateOptions<TData> = {
  184. mutation,
  185. variables,
  186. fetchPolicy: "no-cache",
  187. context: {
  188. headers: {
  189. "Authorization": "Bearer " + token,
  190. },
  191. fetchOptions: {
  192. cache: 'no-store',
  193. },
  194. }
  195. };
  196. const result: ApolloClient.MutateResult<TData> = await client.mutate(mutateOption);
  197. console.log('authorizationGraphqlMutate result ---- ', result);
  198. const resData = result.data || null;
  199. return {data: resData, error: result.error? result.error.message : '' };
  200. } catch (error: any) {
  201. console.log('authorizationGraphqlMutate error ---- ', error.message);
  202. if(operationName === 'GetCartItem' && error.message === 'Cart not found') {
  203. return { data: null, error: 'Cart not found' };
  204. }
  205. throw error;
  206. }
  207. }
  208. export async function authorizationGraphqlQuery<
  209. TData = unknown,
  210. TVariables extends OperationVariables = OperationVariables
  211. >(
  212. query: DocumentNode,
  213. variables?: TVariables,
  214. ): Promise<GraphqlRequestResult<TData>> {
  215. const cookieStore = await cookies();
  216. const isGuest = cookieStore.get(IS_GUEST);
  217. const authToken = cookieStore.get(GUEST_CART_TOKEN);
  218. let token = '';
  219. if(!authToken) {
  220. return {data: null, error: 'Authorization token not found!'};
  221. }
  222. if(isGuest?.value === 'false') { // 登录用户
  223. token = authToken.value;
  224. } else {
  225. // 游客
  226. const jwtRes = decodeJWT<{
  227. sessionToken: string;
  228. cartId: number;
  229. isGuest: boolean;
  230. }>(authToken.value, true);
  231. token = jwtRes?.sessionToken || '';
  232. }
  233. const client = getClient();
  234. try {
  235. const queryOption: ApolloClient.QueryOptions<TData> = {
  236. query,
  237. variables,
  238. fetchPolicy: "no-cache",
  239. context: {
  240. headers: {
  241. "Authorization": "Bearer " + token,
  242. },
  243. fetchOptions: {
  244. cache: 'no-store',
  245. },
  246. }
  247. };
  248. console.log('authorizationGraphqlQuery token ---- ', token);
  249. const result: ApolloClient.QueryResult<TData> = await client.query(queryOption);
  250. console.log('authorizationGraphqlQuery result ---- ', result);
  251. const resData = result.data || null;
  252. return {data: resData, error: result.error? result.error.message : '' };
  253. } catch (error) {
  254. throw error;
  255. }
  256. }
  257. export async function fullCacheGraphqlRequest<
  258. TData = unknown,
  259. TVariables extends OperationVariables = OperationVariables
  260. >(
  261. query: DocumentNode,
  262. variables?: TVariables,
  263. ): Promise<GraphqlRequestResult<TData>> {
  264. const client = getClient();
  265. try {
  266. const queryOption: ApolloClient.QueryOptions<TData> = {
  267. query,
  268. variables,
  269. fetchPolicy: "cache-first",
  270. context: {
  271. fetchOptions: {
  272. cache: 'force-cache',
  273. },
  274. }
  275. };
  276. const result: ApolloClient.QueryResult<TData> = await client.query(queryOption);
  277. console.log('fullCacheGraphqlRequest result ---- ', result);
  278. const resData = result.data || null;
  279. return {data: resData, error: result.error? result.error.message : '' };
  280. } catch (error) {
  281. throw error;
  282. }
  283. }