Skip to content

feat: allow to use custom fetch with generated hooks #556

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"test-ci": "ZENSTACK_TEST=1 pnpm -r run test --silent",
"lint": "pnpm -r lint",
"publish-all": "pnpm --filter \"./packages/**\" -r publish --access public",
"publish-preview": "pnpm --filter \"./packages/**\" -r publish --registry http://localhost:4873"
"publish-preview": "pnpm --filter \"./packages/**\" -r publish --force --registry http://localhost:4873"
},
"keywords": [],
"author": "",
Expand Down
72 changes: 50 additions & 22 deletions packages/plugins/swr/res/helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,32 @@ import { createContext } from 'react';
import type { MutatorCallback, MutatorOptions, SWRResponse } from 'swr';
import useSWR, { useSWRConfig } from 'swr';

/**
* Function signature for `fetch`.
*/
export type FetchFn = (url: string, options?: RequestInit) => Promise<Response>;

/**
* Context type for configuring react hooks.
*/
export type RequestHandlerContext = {
/**
* The endpoint to use for the queries.
*/
endpoint: string;

/**
* A custom fetch function for sending the HTTP requests.
*/
fetch?: FetchFn;
};

/**
* Context for configuring react hooks.
*/
export const RequestHandlerContext = createContext<RequestHandlerContext>({
endpoint: '/api/model',
fetch: undefined,
});

/**
Expand Down Expand Up @@ -43,10 +57,11 @@ export type RequestOptions<T> = {
export function get<Result, Error = any>(
url: string | null,
args?: unknown,
options?: RequestOptions<Result>
options?: RequestOptions<Result>,
fetch?: FetchFn
): SWRResponse<Result, Error> {
const reqUrl = options?.disabled ? null : url ? makeUrl(url, args) : null;
return useSWR<Result, Error>(reqUrl, fetcher, {
return useSWR<Result, Error>(reqUrl, (url) => fetcher(url, undefined, fetch), {
fallbackData: options?.initialData,
});
}
Expand All @@ -58,14 +73,18 @@ export function get<Result, Error = any>(
* @param data The request data.
* @param mutate Mutator for invalidating cache.
*/
export async function post<Result>(url: string, data: unknown, mutate: Mutator): Promise<Result> {
const r: Result = await fetcher(url, {
method: 'POST',
headers: {
'content-type': 'application/json',
export async function post<Result>(url: string, data: unknown, mutate: Mutator, fetch?: FetchFn): Promise<Result> {
const r: Result = await fetcher(
url,
{
method: 'POST',
headers: {
'content-type': 'application/json',
},
body: marshal(data),
},
body: marshal(data),
});
fetch
);
mutate();
return r;
}
Expand All @@ -77,14 +96,18 @@ export async function post<Result>(url: string, data: unknown, mutate: Mutator):
* @param data The request data.
* @param mutate Mutator for invalidating cache.
*/
export async function put<Result>(url: string, data: unknown, mutate: Mutator): Promise<Result> {
const r: Result = await fetcher(url, {
method: 'PUT',
headers: {
'content-type': 'application/json',
export async function put<Result>(url: string, data: unknown, mutate: Mutator, fetch?: FetchFn): Promise<Result> {
const r: Result = await fetcher(
url,
{
method: 'PUT',
headers: {
'content-type': 'application/json',
},
body: marshal(data),
},
body: marshal(data),
});
fetch
);
mutate();
return r;
}
Expand All @@ -96,11 +119,15 @@ export async function put<Result>(url: string, data: unknown, mutate: Mutator):
* @param args The request args object, which will be superjson-stringified and appended as "?q=" parameter
* @param mutate Mutator for invalidating cache.
*/
export async function del<Result>(url: string, args: unknown, mutate: Mutator): Promise<Result> {
export async function del<Result>(url: string, args: unknown, mutate: Mutator, fetch?: FetchFn): Promise<Result> {
const reqUrl = makeUrl(url, args);
const r: Result = await fetcher(reqUrl, {
method: 'DELETE',
});
const r: Result = await fetcher(
reqUrl,
{
method: 'DELETE',
},
fetch
);
const path = url.split('/');
path.pop();
mutate();
Expand Down Expand Up @@ -128,8 +155,9 @@ export function getMutate(prefixes: string[]): Mutator {
};
}

export async function fetcher<R>(url: string, options?: RequestInit) {
const res = await fetch(url, options);
export async function fetcher<R>(url: string, options?: RequestInit, fetch?: FetchFn) {
const _fetch = fetch ?? window.fetch;
const res = await _fetch(url, options);
if (!res.ok) {
const error: Error & { info?: unknown; status?: number } = new Error(
'An error occurred while fetching the data.'
Expand Down
8 changes: 4 additions & 4 deletions packages/plugins/swr/src/generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ function generateModelHooks(project: Project, outDir: string, model: DataModel,
name: `useMutate${model.name}`,
isExported: true,
statements: [
'const { endpoint } = useContext(RequestHandlerContext);',
'const { endpoint, fetch } = useContext(RequestHandlerContext);',
`const prefixesToMutate = [${prefixesToMutate
.map((prefix) => '`${endpoint}/' + lowerCaseFirst(model.name) + '/' + prefix + '`')
.join(', ')}];`,
Expand Down Expand Up @@ -296,8 +296,8 @@ function generateQueryHook(
})
.addBody()
.addStatements([
'const { endpoint } = useContext(RequestHandlerContext);',
`return request.get<${returnType}>(\`\${endpoint}/${modelRouteName}/${operation}\`, args, options);`,
'const { endpoint, fetch } = useContext(RequestHandlerContext);',
`return request.get<${returnType}>(\`\${endpoint}/${modelRouteName}/${operation}\`, args, options, fetch);`,
]);
}

Expand Down Expand Up @@ -327,7 +327,7 @@ function generateMutation(
.addBody()
.addStatements([
wrapReadbackErrorCheck(
`return await request.${fetcherFunc}<${returnType}>(\`\${endpoint}/${modelRouteName}/${operation}\`, args, mutate);`
`return await request.${fetcherFunc}<${returnType}>(\`\${endpoint}/${modelRouteName}/${operation}\`, args, mutate, fetch);`
),
]);
return funcName;
Expand Down
50 changes: 33 additions & 17 deletions packages/plugins/tanstack-query/res/react/helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { createContext } from 'react';
*/
export const RequestHandlerContext = createContext<RequestHandlerContext>({
endpoint: DEFAULT_QUERY_ENDPOINT,
fetch: undefined,
});

/**
Expand All @@ -33,11 +34,11 @@ export const Provider = RequestHandlerContext.Provider;
* @returns useQuery hook
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function query<R>(model: string, url: string, args?: unknown, options?: UseQueryOptions<R>) {
export function query<R>(model: string, url: string, args?: unknown, options?: UseQueryOptions<R>, fetch?: FetchFn) {
const reqUrl = makeUrl(url, args);
return useQuery<R>({
queryKey: [QUERY_KEY_PREFIX + model, url, args],
queryFn: () => fetcher<R>(reqUrl),
queryFn: () => fetcher<R>(reqUrl, undefined, fetch),
...options,
});
}
Expand All @@ -55,17 +56,22 @@ export function postMutation<T, R = any>(
model: string,
url: string,
options?: Omit<UseMutationOptions<R, unknown, T>, 'mutationFn'>,
fetch?: FetchFn,
invalidateQueries = true
) {
const queryClient = useQueryClient();
const mutationFn = (data: any) =>
fetcher<R>(url, {
method: 'POST',
headers: {
'content-type': 'application/json',
fetcher<R>(
url,
{
method: 'POST',
headers: {
'content-type': 'application/json',
},
body: marshal(data),
},
body: marshal(data),
});
fetch
);

const finalOptions = mergeOptions<T, R>(model, options, invalidateQueries, mutationFn, queryClient);
const mutation = useMutation<R, unknown, T>(finalOptions);
Expand All @@ -85,17 +91,22 @@ export function putMutation<T, R = any>(
model: string,
url: string,
options?: Omit<UseMutationOptions<R, unknown, T>, 'mutationFn'>,
fetch?: FetchFn,
invalidateQueries = true
) {
const queryClient = useQueryClient();
const mutationFn = (data: any) =>
fetcher<R>(url, {
method: 'PUT',
headers: {
'content-type': 'application/json',
fetcher<R>(
url,
{
method: 'PUT',
headers: {
'content-type': 'application/json',
},
body: marshal(data),
},
body: marshal(data),
});
fetch
);

const finalOptions = mergeOptions<T, R>(model, options, invalidateQueries, mutationFn, queryClient);
const mutation = useMutation<R, unknown, T>(finalOptions);
Expand All @@ -115,13 +126,18 @@ export function deleteMutation<T, R = any>(
model: string,
url: string,
options?: Omit<UseMutationOptions<R, unknown, T>, 'mutationFn'>,
fetch?: FetchFn,
invalidateQueries = true
) {
const queryClient = useQueryClient();
const mutationFn = (data: any) =>
fetcher<R>(makeUrl(url, data), {
method: 'DELETE',
});
fetcher<R>(
makeUrl(url, data),
{
method: 'DELETE',
},
fetch
);

const finalOptions = mergeOptions<T, R>(model, options, invalidateQueries, mutationFn, queryClient);
const mutation = useMutation<R, unknown, T>(finalOptions);
Expand Down
18 changes: 16 additions & 2 deletions packages/plugins/tanstack-query/res/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,29 @@ export const DEFAULT_QUERY_ENDPOINT = '/api/model';
*/
export const QUERY_KEY_PREFIX = 'zenstack:';

/**
* Function signature for `fetch`.
*/
export type FetchFn = (url: string, options?: RequestInit) => Promise<Response>;

/**
* Context type for configuring the hooks.
*/
export type RequestHandlerContext = {
/**
* The endpoint to use for the queries.
*/
endpoint: string;

/**
* A custom fetch function for sending the HTTP requests.
*/
fetch?: FetchFn;
};

async function fetcher<R>(url: string, options?: RequestInit) {
const res = await fetch(url, options);
async function fetcher<R>(url: string, options?: RequestInit, fetch?: FetchFn) {
const _fetch = fetch ?? window.fetch;
const res = await _fetch(url, options);
if (!res.ok) {
const error: Error & { info?: unknown; status?: number } = new Error(
'An error occurred while fetching the data.'
Expand Down
49 changes: 32 additions & 17 deletions packages/plugins/tanstack-query/res/svelte/helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,11 @@ export const SvelteQueryContextKey = 'zenstack-svelte-query-context';
* @returns useQuery hook
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function query<R>(model: string, url: string, args?: unknown, options?: QueryOptions<R>) {
export function query<R>(model: string, url: string, args?: unknown, options?: QueryOptions<R>, fetch?: FetchFn) {
const reqUrl = makeUrl(url, args);
return createQuery<R>({
queryKey: [QUERY_KEY_PREFIX + model, url, args],
queryFn: () => fetcher<R>(reqUrl),
queryFn: () => fetcher<R>(reqUrl, undefined, fetch),
...options,
});
}
Expand All @@ -47,17 +47,22 @@ export function postMutation<T, R = any>(
model: string,
url: string,
options?: Omit<MutationOptions<R, unknown, T>, 'mutationFn'>,
fetch?: FetchFn,
invalidateQueries = true
) {
const queryClient = useQueryClient();
const mutationFn = (data: any) =>
fetcher<R>(url, {
method: 'POST',
headers: {
'content-type': 'application/json',
fetcher<R>(
url,
{
method: 'POST',
headers: {
'content-type': 'application/json',
},
body: marshal(data),
},
body: marshal(data),
});
fetch
);

const finalOptions = mergeOptions<T, R>(model, options, invalidateQueries, mutationFn, queryClient);
const mutation = createMutation<R, unknown, T>(finalOptions);
Expand All @@ -77,17 +82,22 @@ export function putMutation<T, R = any>(
model: string,
url: string,
options?: Omit<MutationOptions<R, unknown, T>, 'mutationFn'>,
fetch?: FetchFn,
invalidateQueries = true
) {
const queryClient = useQueryClient();
const mutationFn = (data: any) =>
fetcher<R>(url, {
method: 'PUT',
headers: {
'content-type': 'application/json',
fetcher<R>(
url,
{
method: 'PUT',
headers: {
'content-type': 'application/json',
},
body: marshal(data),
},
body: marshal(data),
});
fetch
);

const finalOptions = mergeOptions<T, R>(model, options, invalidateQueries, mutationFn, queryClient);
const mutation = createMutation<R, unknown, T>(finalOptions);
Expand All @@ -107,13 +117,18 @@ export function deleteMutation<T, R = any>(
model: string,
url: string,
options?: Omit<MutationOptions<R, unknown, T>, 'mutationFn'>,
fetch?: FetchFn,
invalidateQueries = true
) {
const queryClient = useQueryClient();
const mutationFn = (data: any) =>
fetcher<R>(makeUrl(url, data), {
method: 'DELETE',
});
fetcher<R>(
makeUrl(url, data),
{
method: 'DELETE',
},
fetch
);

const finalOptions = mergeOptions<T, R>(model, options, invalidateQueries, mutationFn, queryClient);
const mutation = createMutation<R, unknown, T>(finalOptions);
Expand Down
Loading