Skip to content

Commit d2bad3a

Browse files
committed
feat: added strict client fetcher
1 parent f816df6 commit d2bad3a

File tree

4 files changed

+84
-11
lines changed

4 files changed

+84
-11
lines changed

src/client.ts

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { DocumentTypeDecoration } from "@graphql-typed-document-node/core";
2-
import { print } from "graphql";
2+
import {GraphQLError, print} from "graphql";
33
import { isNode } from "graphql/language/ast.js";
44
import {
55
createRequest,
@@ -16,6 +16,7 @@ import {
1616
mergeHeaders,
1717
type GqlResponse,
1818
} from "./helpers";
19+
import {toe} from "graphql-toe";
1920

2021
type Options = {
2122
/**
@@ -60,6 +61,36 @@ type RequestOptions = {
6061
headers?: Headers | Record<string, string>;
6162
};
6263

64+
export type StrictClientFetcher = <TResponse extends Record<string, any>, TVariables>(
65+
astNode: DocumentTypeDecoration<TResponse, TVariables>,
66+
variables?: TVariables,
67+
options?: RequestOptions,
68+
) => Promise<TResponse>;
69+
70+
// Wraps the initServerFetcher function, which returns the result wrapped in the graphql-toe library. This will throw
71+
// an error if a field is used that had an entry in the error response array
72+
export const initStrictClientFetcher = (url: string, options: Options = {}): StrictClientFetcher => {
73+
const fetcher = initClientFetcher(url, options);
74+
return async <TResponse extends Record<string, any>, TVariables>(
75+
astNode: DocumentTypeDecoration<TResponse, TVariables>,
76+
variables?: TVariables,
77+
options?: RequestOptions,
78+
): Promise<TResponse> => {
79+
const response = await fetcher(
80+
astNode,
81+
variables,
82+
options,
83+
);
84+
85+
return toe<TResponse>(
86+
response as unknown as {
87+
data?: TResponse | null | undefined;
88+
errors?: readonly GraphQLError[] | undefined;
89+
},
90+
);
91+
};
92+
};
93+
6394
export type ClientFetcher = <TResponse, TVariables>(
6495
astNode: DocumentTypeDecoration<TResponse, TVariables>,
6596
variables?: TVariables,

src/index.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
export { initClientFetcher } from "./client";
2-
export type { ClientFetcher } from "./client";
3-
export { ClientGqlFetcherProvider, useClientGqlFetcher } from "./provider";
4-
export type { GraphQLError, GqlResponse } from "./helpers";
1+
export {initClientFetcher, initStrictClientFetcher} from "./client";
2+
export type {ClientFetcher, StrictClientFetcher} from "./client";
3+
export {ClientGqlFetcherProvider, useClientGqlFetcher} from "./provider";
4+
export {StrictClientGqlFetcherProvider, useStrictClientGqlFetcher} from "./strict-provider";
5+
export type {GraphQLError, GqlResponse} from "./helpers";

src/request.ts

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,4 @@
1-
import type { DocumentTypeDecoration } from "@graphql-typed-document-node/core";
21
import { createSha256, extractOperationName, pruneObject } from "helpers";
3-
4-
export type DocumentIdFn = <TResult, TVariables>(
5-
query: DocumentTypeDecoration<TResult, TVariables>,
6-
) => string | undefined;
7-
82
export type GraphQLRequest<TVariables> = {
93
operationName: string;
104
query: string | undefined;

src/strict-provider.tsx

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import type {ClientFetcher, StrictClientFetcher} from "client";
2+
import { createContext, useContext } from "react";
3+
import invariant from "tiny-invariant";
4+
import type { PropsWithChildren } from "react";
5+
6+
/**
7+
* Context to provide the fetcher for the API used during client side calls
8+
*/
9+
export const StrictClientGqlFetcherContext = createContext<StrictClientFetcher | undefined>(
10+
undefined,
11+
);
12+
13+
export type StrictClientGqlFetcherProviderProps = PropsWithChildren<{
14+
fetcher: StrictClientFetcher;
15+
}>;
16+
17+
/**
18+
* Provides the fetcher that should be used for client side calls to the React context
19+
*/
20+
export const StrictClientGqlFetcherProvider = ({
21+
children,
22+
fetcher,
23+
}: StrictClientGqlFetcherProviderProps) => (
24+
<StrictClientGqlFetcherContext.Provider value={fetcher}>
25+
{children}
26+
</StrictClientGqlFetcherContext.Provider>
27+
);
28+
29+
/**
30+
* React hook to get the fetcher that should be used for client side calls
31+
*/
32+
export const useStrictClientGqlFetcher = (): StrictClientFetcher => {
33+
const context = useContext(StrictClientGqlFetcherContext);
34+
35+
if (context === undefined) {
36+
if ("production" !== process.env.NODE_ENV) {
37+
invariant(
38+
false,
39+
"useStrictClientGqlFetcher must be used within a ClientGqlFetcherProvider",
40+
);
41+
} else {
42+
invariant(false);
43+
}
44+
}
45+
46+
return context as StrictClientFetcher;
47+
};

0 commit comments

Comments
 (0)