Closed psteinroe closed 1 year ago
from https://github.com/psteinroe/supabase-cache-helpers/issues/53
Curious on your approach to how you created the cache keys? React Query out of the box will infer the type of the Supabase client query, but the cache keys are still totally up in the air. I was hoping there would be a way to infer the cache keys from the query itself, but it's complex. The URL params that are sent to the rest client would be awesome, but not sure how to get them within the request, or make some sort of generic component that could do it all. I asked about doing this in a round about way https://github.com/supabase/supabase/discussions/9530#discussioncomment-3978819.
from #53
Curious on your approach to how you created the cache keys? React Query out of the box will infer the type of the Supabase client query, but the cache keys are still totally up in the air. I was hoping there would be a way to infer the cache keys from the query itself, but it's complex. The URL params that are sent to the rest client would be awesome, but not sure how to get them within the request, or make some sort of generic component that could do it all. I asked about doing this in a round about way supabase/supabase#9530 (reply in thread).
the query key can be easily generated using the PostgrestParser class: https://github.com/psteinroe/supabase-cache-helpers/blob/main/packages/postgrest-filter/src/postgrest-parser.ts
You can use the SWR implementation as a reference, where a PostgrestParser is created from the PostgrestFilterBuilder https://github.com/psteinroe/supabase-cache-helpers/blob/cc96081d0aa52944d3ba7ac2c2096cba634a4a9d/packages/postgrest-swr/src/lib/middleware.ts#L28
The key (which is just a string for SWR), is then simply a concatenation of all relevant properties https://github.com/psteinroe/supabase-cache-helpers/blob/cc96081d0aa52944d3ba7ac2c2096cba634a4a9d/packages/postgrest-swr/src/lib/encode.ts#L6-L22
I would be very happy to collaborate here!
I think the main difference between swr and react-query is the lack of middleware in the latter. In the swr implementation, the middleware takes care of encoding the key from the PostgrestFilterBuilder
instance into a string while forwarding the instance to the fetcher. For react-query, it does not seem like its possible to use a different key.
I guess something like this should work, where createFetcher
is imported from postgrest-fetcher
and PostgrestParser
from postgrest-filter
.
useQuery(encode(new PostgrestParser(query)), createFetcher({...})(query))
encode
could look like this
export const encode = <
Schema extends GenericSchema,
Table extends Record<string, unknown>,
Result
>(
parser: PostgrestParser<Schema, Table, Result>
) => {
return [
KEY_PREFIX,
parser.schema ?? DEFAULT_SCHEMA_NAME,
parser.table,
parser.queryKey,
parser.bodyKey ?? null,
parser.count ?? null,
parser.isHead ?? null,
];
};
@psteinroe - Awesome! Have been poking around with using just the PostgresParser
to generate the cache keys only, and passing that into React Query as is and it looks promising. So a setup is looking like this.
import { useQuery } from "@tanstack/react-query";
import { client } from "@supabase/supabase-js";
import { PostgrestParser } from "@supabase-cache-helpers/postgrest-filter";
function example() {
const query = client.from("table").select("id");
const queryKey = new PostgrestParser(query).queryKey;
const table = useQuery([queryKey], async () => query);
}
Abstracting this into another function that can be reused across the app is a hurdle since it's running into inferring the types from the client. Eg
import { useQuery, UseQueryOptions } from "@tanstack/react-query";
import { PostgrestParser } from "@supabase-cache-helpers/postgrest-filter";
// query arg inferred as any 😐
function generalUseQuery(query, options?: UseQueryOptions) {
const queryKey = new PostgrestParser(query).queryKey;
return useQuery([queryKey], async () => query, options);
}
// This would be awesome cause the client could just use
const reactQueryData = generalUseQuery(client.from("table").select("column"));
Another note - I'm noticing that PostgresParser
wont accept a single()
method on a query, and that the main cache helper using SWR passes in those methods as a string argument to your query instead of letting you tack them onto the query as if you're using the Supabase client out of the box. I've been using single()
in lots of places and not a big deal to refactor, but the main cache helper supporting 'single' and the parser not seems to be forcing me to not use single in the queries.
Hey @drewbietron,
thanks for looking into this. I did a quick draft but still get some type errors. To fix it, I will need a better understanding of how react query works and especially how the returned types are computed. But maybe it helps you. Note that this is an ugly draft and eg useCallback
is missing.
import {
useQuery as useReactQuery,
UseQueryOptions as UseReactQueryOptions,
UseQueryResult as UseReactQueryResult,
QueryFunction as ReactQueryFunction,
QueryKey,
} from "@tanstack/react-query";
import {
PostgrestFilterBuilder,
PostgrestError,
PostgrestResponse,
} from "@supabase/postgrest-js";
import {
createFetcher,
FetcherType,
PostgrestFetcherResponse,
} from "@supabase-cache-helpers/postgrest-fetcher";
import { GenericSchema } from "@supabase/postgrest-js/dist/module/types";
import { encode } from "../lib";
import { PostgrestParser } from "@supabase-cache-helpers/postgrest-filter";
/**
* Perform a postgrest query
* @param query
* @param mode
* @param config
*/
function useQuery<
Schema extends GenericSchema,
Table extends Record<string, unknown>,
Result
>(
query: PostgrestFilterBuilder<Schema, Table, Result> | null,
mode: "single",
config?: UseReactQueryOptions<
PostgrestFetcherResponse<Result>,
PostgrestError
>
): UseReactQueryResult<Result, PostgrestError>;
function useQuery<
Schema extends GenericSchema,
Table extends Record<string, unknown>,
Result
>(
query: PostgrestFilterBuilder<Schema, Table, Result> | null,
mode: "maybeSingle",
config?: UseReactQueryOptions<
PostgrestFetcherResponse<Result | undefined>,
PostgrestError
>
): UseReactQueryResult<Result | undefined, PostgrestError>;
function useQuery<
Schema extends GenericSchema,
Table extends Record<string, unknown>,
Result
>(
query: PostgrestFilterBuilder<Schema, Table, Result> | null,
mode: "multiple",
config?: UseReactQueryOptions<
PostgrestFetcherResponse<Result | undefined>,
PostgrestError
>
): UseReactQueryResult<Result[], PostgrestError> &
Pick<PostgrestResponse<Result[]>, "count">;
function useQuery<
Schema extends GenericSchema,
Table extends Record<string, unknown>,
Result
>(
query: PostgrestFilterBuilder<Schema, Table, Result> | null,
mode: FetcherType,
config?: UseReactQueryOptions<
PostgrestFetcherResponse<Result | Result[] | undefined>,
PostgrestError
>
): UseReactQueryResult<Result | Result[] | undefined, PostgrestError> &
Partial<Pick<PostgrestResponse<Result | Result[]>, "count">> {
const queryFn: ReactQueryFunction<
PostgrestFetcherResponse<Result | Result[]>,
QueryKey
> = ({ meta, queryKey, pageParam, signal }) =>
createFetcher<Schema, Table, Result>(mode)(query);
const res = useReactQuery(
encode(new PostgrestParser(query)),
queryFn,
config
);
if (!res.data.data) return { data: res.data.data, ...res };
const { data, count } = res.data;
return { data, count, ...res };
}
export { useQuery };
encode looks like this:
import { PostgrestParser } from "@supabase-cache-helpers/postgrest-filter";
import { KEY_PREFIX } from "./constants";
import { DEFAULT_SCHEMA_NAME } from "@supabase-cache-helpers/postgrest-shared";
import { GenericSchema } from "@supabase/postgrest-js/dist/module/types";
export const encode = <
Schema extends GenericSchema,
Table extends Record<string, unknown>,
Result
>(
parser: PostgrestParser<Schema, Table, Result>
) => {
return [
KEY_PREFIX,
parser.schema ?? DEFAULT_SCHEMA_NAME,
parser.table,
parser.queryKey,
parser.bodyKey ?? "null",
`count=${parser.count}`,
`head=${parser.isHead}`,
];
};
@drewbietron I played around with the types and it essentially boils down to finding a solution to the problem I just asked on SO: https://stackoverflow.com/questions/74336828/derive-value-of-generic-from-parameter
Another note - I'm noticing that
PostgresParser
wont accept asingle()
method on a query, and that the main cache helper using SWR passes in those methods as a string argument to your query instead of letting you tack them onto the query as if you're using the Supabase client out of the box. I've been usingsingle()
in lots of places and not a big deal to refactor, but the main cache helper supporting 'single' and the parser not seems to be forcing me to not use single in the queries.
@drewbietron Using single and multiple for SWR allows me to correctly type the return type of useQuery
without fixing the aforementioned problem. Nevertheless, I will soon release a new version of postgrest-filter
, and PostgrestParser
now accepts PostgrestBuilder
. This allows passing queries with .single()
and .multiple()
.
Hi @psteinroe. I'm working on supabase-query which seems like a subset of this library (with a Context wrapper to pass the client directly to useQuery/useMutation
). I made the library back in April 2022 before Supabase v2 and tanstack-query was released and I'm looking to upgrade it. What is the current status on this issue? Maybe we can collaborate.
Hi @HermanNygaard thanks for reaching out! I would love to collaborate on this. I am currently doing a rewrite of the packages in #128 . As part of this, I plan to do a PoC for react-query
. Goal is to make sure that the APIs of the shared packages work for both SWR
and react-query
. I would propose that I ping you once that PoC is done. You can take over then, and push the react-query
implementation. What do you think?
Sounds good, looks promising! I was also wondering about the type inference issue for v2 mentioned here and in #53 , did you or @drewbietron ever figure that out?
@HermanNygaard yes, I did. After the rewrite, you can pass PostgrestBuilder
directly and the types are inferred correct. You can track the progress in the rewrite PR. The only major box left to tick is the mutation issue. The rest are minor dx improvements. The new useQuery
without the mode
param is already there. 🕺
@HermanNygaard I published a pre-release of the react-query package yesterday. Check out the rewrite PR for details!
also @drewbietron would love your feedback.
I published @supabase-cache-helpers/react-query
last night! Pagination and Infinite Scroll are still missing though. Will close this and add new issues.
This issue is for tracking react query support. Since I have zero experience with react-query, it would be great to find contributors to collaborate with.