Query rework part1

This commit is contained in:
Zoe Roux 2025-02-02 15:25:04 +01:00
parent 32ecf25321
commit 7a4e203cdd
No known key found for this signature in database
6 changed files with 68 additions and 143 deletions

View File

@ -1,33 +0,0 @@
/*
* Kyoo - A portable and vast media library solution.
* Copyright (c) Kyoo.
*
* See AUTHORS.md and LICENSE file in the project root for full license information.
*
* Kyoo is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Kyoo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
*/
/**
* The list of errors that where made in the request.
*/
export interface KyooErrors {
/**
* The list of errors that where made in the request.
*
* @example `["InvalidFilter: no field 'startYear' on a collection"]`
*/
errors: string[];
status?: number | "aborted" | "parse" | "json";
}

View File

@ -1,66 +0,0 @@
/*
* Kyoo - A portable and vast media library solution.
* Copyright (c) Kyoo.
*
* See AUTHORS.md and LICENSE file in the project root for full license information.
*
* Kyoo is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Kyoo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
*/
import { z } from "zod";
/**
* A page of resource that contains information about the pagination of resources.
*/
export interface Page<T> {
/**
* The link of the current page.
*
* @format uri
*/
this: string;
/**
* The link of the first page.
*
* @format uri
*/
first: string;
/**
* The link of the next page.
*
* @format uri
*/
next: string | null;
/**
* The number of items in the current page.
*/
count: number;
/**
* The list of items in the page.
*/
items: T[];
}
export const Paged = <Item>(item: z.ZodType<Item>): z.ZodSchema<Page<Item>> =>
z.object({
this: z.string(),
first: z.string(),
next: z.string().nullable(),
count: z.number(),
items: z.array(item),
});

View File

@ -0,0 +1,5 @@
export interface KyooError {
status: number | string;
message: string;
details?: any;
}

46
front/src/models/page.ts Normal file
View File

@ -0,0 +1,46 @@
import { z } from "zod";
/**
* A page of resource that contains information about the pagination of resources.
*/
export interface Page<T> {
/**
* The link of the current page.
*
* @format uri
*/
this: string;
/**
* The link of the first page.
*
* @format uri
*/
first: string;
/**
* The link of the next page.
*
* @format uri
*/
next: string | null;
/**
* The number of items in the current page.
*/
count: number;
/**
* The list of items in the page.
*/
items: T[];
}
export const Paged = <Item>(item: z.ZodType<Item>): z.ZodSchema<Page<Item>> =>
z.object({
this: z.string(),
first: z.string(),
next: z.string().nullable(),
count: z.number(),
items: z.array(item),
});

View File

@ -1,23 +1,3 @@
/*
* Kyoo - A portable and vast media library solution.
* Copyright (c) Kyoo.
*
* See AUTHORS.md and LICENSE file in the project root for full license information.
*
* Kyoo is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Kyoo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
*/
import { import {
QueryClient, QueryClient,
type QueryFunctionContext, type QueryFunctionContext,
@ -26,9 +6,8 @@ import {
} from "@tanstack/react-query"; } from "@tanstack/react-query";
import type { ComponentType, ReactElement } from "react"; import type { ComponentType, ReactElement } from "react";
import type { z } from "zod"; import type { z } from "zod";
import { getCurrentApiUrl } from "."; import type { KyooError } from "./kyoo-error";
import type { KyooErrors } from "./kyoo-errors"; // import { getToken, getTokenWJ } from "./login";
import { getToken, getTokenWJ } from "./login";
import { type Page, Paged } from "./page"; import { type Page, Paged } from "./page";
export let lastUsedUrl: string = null!; export let lastUsedUrl: string = null!;
@ -89,12 +68,12 @@ export const queryFn = async <Parser extends z.ZodTypeAny>(
}); });
} catch (e) { } catch (e) {
if (typeof e === "object" && e && "name" in e && e.name === "AbortError") if (typeof e === "object" && e && "name" in e && e.name === "AbortError")
throw { errors: ["Aborted"] } as KyooErrors; throw { message: "Aborted", status: "aborted" } as KyooError;
console.log("Fetch error", e, path); console.log("Fetch error", e, path);
throw { errors: ["Could not reach Kyoo's server."], status: "aborted" } as KyooErrors; throw { message: "Could not reach Kyoo's server.", status: "aborted" } as KyooError;
} }
if (resp.status === 404) { if (resp.status === 404) {
throw { errors: ["Resource not found."], status: 404 } as KyooErrors; throw { message: "Resource not found.", status: 404 } as KyooError;
} }
// If we got a forbidden, try to refresh the token // If we got a forbidden, try to refresh the token
// if we got a token as an argument, it either means we already retried or we go one provided that's fresh // if we got a token as an argument, it either means we already retried or we go one provided that's fresh
@ -110,7 +89,7 @@ export const queryFn = async <Parser extends z.ZodTypeAny>(
try { try {
data = JSON.parse(error); data = JSON.parse(error);
} catch (e) { } catch (e) {
data = { errors: [error] } as KyooErrors; data = { message: error } as KyooError;
} }
data.status = resp.status; data.status = resp.status;
console.trace( console.trace(
@ -120,7 +99,7 @@ export const queryFn = async <Parser extends z.ZodTypeAny>(
data, data,
resp.status, resp.status,
); );
throw data as KyooErrors; throw data as KyooError;
} }
if (resp.status === 204) return null; if (resp.status === 204) return null;
@ -132,18 +111,17 @@ export const queryFn = async <Parser extends z.ZodTypeAny>(
data = await resp.json(); data = await resp.json();
} catch (e) { } catch (e) {
console.error("Invalid json from kyoo", e); console.error("Invalid json from kyoo", e);
throw { errors: ["Invalid response from kyoo"], status: "json" }; throw { message: "Invalid response from kyoo", status: "json" } as KyooError;
} }
if (!type) return data; if (!type) return data;
const parsed = await type.safeParseAsync(data); const parsed = await type.safeParseAsync(data);
if (!parsed.success) { if (!parsed.success) {
console.log("Path: ", path, " Response: ", resp.status, " Parse error: ", parsed.error); console.log("Path: ", path, " Response: ", resp.status, " Parse error: ", parsed.error);
throw { throw {
errors: [
"Invalid response from kyoo. Possible version mismatch between the server and the application.",
],
status: "parse", status: "parse",
} as KyooErrors; message:
"Invalid response from kyoo. Possible version mismatch between the server and the application.",
} as KyooError;
} }
return parsed.data; return parsed.data;
}; };
@ -218,7 +196,7 @@ export const toQueryKey = (query: {
}; };
export const useFetch = <Data,>(query: QueryIdentifier<Data>) => { export const useFetch = <Data,>(query: QueryIdentifier<Data>) => {
return useQuery<Data, KyooErrors>({ return useQuery<Data, KyooError>({
queryKey: toQueryKey(query), queryKey: toQueryKey(query),
queryFn: (ctx) => queryFn: (ctx) =>
queryFn( queryFn(
@ -235,7 +213,7 @@ export const useFetch = <Data,>(query: QueryIdentifier<Data>) => {
}; };
export const useInfiniteFetch = <Data, Ret>(query: QueryIdentifier<Data, Ret>) => { export const useInfiniteFetch = <Data, Ret>(query: QueryIdentifier<Data, Ret>) => {
const ret = useInfiniteQuery<Page<Data>, KyooErrors>({ const ret = useInfiniteQuery<Page<Data>, KyooError>({
queryKey: toQueryKey(query), queryKey: toQueryKey(query),
queryFn: (ctx) => queryFn: (ctx) =>
queryFn( queryFn(
@ -258,10 +236,6 @@ export const useInfiniteFetch = <Data, Ret>(query: QueryIdentifier<Data, Ret>) =
}; };
export const fetchQuery = async (queries: QueryIdentifier[], authToken?: string | null) => { export const fetchQuery = async (queries: QueryIdentifier[], authToken?: string | null) => {
// we can't put this check in a function because we want build time optimizations
// see https://github.com/vercel/next.js/issues/5354 for details
if (typeof window !== "undefined") return null;
const client = createQueryClient(); const client = createQueryClient();
await Promise.all( await Promise.all(
queries.map((query) => { queries.map((query) => {

View File

@ -1,12 +1,11 @@
import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { QueryClientProvider } from "@tanstack/react-query";
import { ComponentType, type ReactNode, useState } from "react"; import { type ReactNode, useState } from "react";
import { createQueryClient } from "~/models/query";
// import { useUserTheme } from "@kyoo/models"; // import { useUserTheme } from "@kyoo/models";
// import { createQueryClient } from "@kyoo/models";
import { ThemeSelector } from "~/primitives/theme"; import { ThemeSelector } from "~/primitives/theme";
const QueryProvider = ({ children }: { children: ReactNode }) => { const QueryProvider = ({ children }: { children: ReactNode }) => {
// const [queryClient] = useState(() => createQueryClient()); const [queryClient] = useState(() => createQueryClient());
const [queryClient] = useState(() => new QueryClient({}));
return <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>; return <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>;
}; };