graphql-fetch.ts 6.6 KB

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