mirror of
https://github.com/shishantbiswas/bknd.git
synced 2026-03-16 12:37:20 +00:00
replaced all react-query usages with new hooks + removed react-query
This commit is contained in:
@@ -1,12 +1,7 @@
|
||||
import type { ModuleConfigs, ModuleSchemas } from "modules";
|
||||
import { getDefaultConfig, getDefaultSchema } from "modules/ModuleManager";
|
||||
import { createContext, startTransition, useContext, useEffect, useRef, useState } from "react";
|
||||
import { Logo } from "ui/components/display/Logo";
|
||||
import { Link } from "ui/components/wouter/Link";
|
||||
import * as AppShell from "ui/layouts/AppShell/AppShell";
|
||||
import { HeaderNavigation } from "ui/layouts/AppShell/Header";
|
||||
import { Root } from "ui/routes/root";
|
||||
import type { ModuleConfigs, ModuleSchemas } from "../../modules";
|
||||
import { useClient } from "./ClientProvider";
|
||||
import { useApi } from "ui/client";
|
||||
import { type TSchemaActions, getSchemaActions } from "./schema/actions";
|
||||
import { AppReduced } from "./utils/AppReduced";
|
||||
|
||||
@@ -38,7 +33,7 @@ export function BkndProvider({
|
||||
useState<Pick<BkndContext, "version" | "schema" | "config" | "permissions">>();
|
||||
const [fetched, setFetched] = useState(false);
|
||||
const errorShown = useRef<boolean>();
|
||||
const client = useClient();
|
||||
const api = useApi();
|
||||
|
||||
async function reloadSchema() {
|
||||
await fetchSchema(includeSecrets, true);
|
||||
@@ -46,7 +41,7 @@ export function BkndProvider({
|
||||
|
||||
async function fetchSchema(_includeSecrets: boolean = false, force?: boolean) {
|
||||
if (withSecrets && !force) return;
|
||||
const res = await client.api.system.readSchema({
|
||||
const res = await api.system.readSchema({
|
||||
config: true,
|
||||
secrets: _includeSecrets
|
||||
});
|
||||
@@ -100,7 +95,7 @@ export function BkndProvider({
|
||||
|
||||
if (!fetched || !schema) return fallback;
|
||||
const app = new AppReduced(schema?.config as any);
|
||||
const actions = getSchemaActions({ client, setSchema, reloadSchema });
|
||||
const actions = getSchemaActions({ api, setSchema, reloadSchema });
|
||||
|
||||
return (
|
||||
<BkndContext.Provider value={{ ...schema, actions, requireSecrets, app, adminOverride }}>
|
||||
|
||||
@@ -1,22 +1,10 @@
|
||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
import type { TApiUser } from "Api";
|
||||
import { Api, type ApiOptions, type TApiUser } from "Api";
|
||||
import { createContext, useContext, useEffect, useState } from "react";
|
||||
//import { useBkndWindowContext } from "ui/client/BkndProvider";
|
||||
import { AppQueryClient } from "./utils/AppQueryClient";
|
||||
|
||||
const ClientContext = createContext<{ baseUrl: string; client: AppQueryClient }>({
|
||||
const ClientContext = createContext<{ baseUrl: string; api: Api }>({
|
||||
baseUrl: undefined
|
||||
} as any);
|
||||
|
||||
export const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
retry: false,
|
||||
refetchOnWindowFocus: false
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export type ClientProviderProps = {
|
||||
children?: any;
|
||||
baseUrl?: string;
|
||||
@@ -49,47 +37,34 @@ export const ClientProvider = ({ children, baseUrl, user }: ClientProviderProps)
|
||||
return null; // or a loader/spinner if desired
|
||||
}
|
||||
|
||||
//console.log("client provider11 with", { baseUrl, fallback: actualBaseUrl, user });
|
||||
const client = createClient(actualBaseUrl, user ?? winCtx.user);
|
||||
const api = new Api({ host: actualBaseUrl, user: user ?? winCtx.user });
|
||||
|
||||
return (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<ClientContext.Provider value={{ baseUrl: actualBaseUrl, client }}>
|
||||
{children}
|
||||
</ClientContext.Provider>
|
||||
</QueryClientProvider>
|
||||
<ClientContext.Provider value={{ baseUrl: actualBaseUrl, api }}>
|
||||
{children}
|
||||
</ClientContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export function createClient(baseUrl: string, user?: object) {
|
||||
return new AppQueryClient(baseUrl, user);
|
||||
}
|
||||
|
||||
export function createOrUseClient(baseUrl: string) {
|
||||
export const useApi = (host?: ApiOptions["host"]) => {
|
||||
const context = useContext(ClientContext);
|
||||
if (!context) {
|
||||
console.warn("createOrUseClient returned a new client");
|
||||
return createClient(baseUrl);
|
||||
if (host && host !== context.baseUrl) {
|
||||
return new Api({ host });
|
||||
}
|
||||
|
||||
return context.client;
|
||||
}
|
||||
|
||||
export const useClient = () => {
|
||||
const context = useContext(ClientContext);
|
||||
if (!context) {
|
||||
throw new Error("useClient must be used within a ClientProvider");
|
||||
}
|
||||
return context.client;
|
||||
return context.api;
|
||||
};
|
||||
|
||||
/**
|
||||
* @deprecated use useApi().baseUrl instead
|
||||
*/
|
||||
export const useBaseUrl = () => {
|
||||
const context = useContext(ClientContext);
|
||||
return context.baseUrl;
|
||||
};
|
||||
|
||||
type BkndWindowContext = {
|
||||
user?: object;
|
||||
user?: TApiUser;
|
||||
logout_route: string;
|
||||
};
|
||||
export function useBkndWindowContext(): BkndWindowContext {
|
||||
|
||||
@@ -1,12 +1,7 @@
|
||||
import type { Api } from "Api";
|
||||
import type { FetchPromise, ResponseObject } from "modules/ModuleApi";
|
||||
import useSWR, { type SWRConfiguration } from "swr";
|
||||
import { useClient } from "ui/client/ClientProvider";
|
||||
|
||||
export const useApi = () => {
|
||||
const client = useClient();
|
||||
return client.api;
|
||||
};
|
||||
import useSWR, { type SWRConfiguration, useSWRConfig } from "swr";
|
||||
import { useApi } from "ui/client";
|
||||
|
||||
export const useApiQuery = <
|
||||
Data,
|
||||
@@ -21,7 +16,7 @@ export const useApiQuery = <
|
||||
const fetcher = () => promise.execute().then(refine);
|
||||
const key = promise.key();
|
||||
|
||||
type RefinedData = RefineFn extends (data: Data) => infer R ? R : Data;
|
||||
type RefinedData = RefineFn extends (data: ResponseObject<Data>) => infer R ? R : Data;
|
||||
|
||||
const swr = useSWR<RefinedData>(options?.enabled === false ? null : key, fetcher, options);
|
||||
return {
|
||||
@@ -31,3 +26,13 @@ export const useApiQuery = <
|
||||
api
|
||||
};
|
||||
};
|
||||
|
||||
export const useInvalidate = () => {
|
||||
const mutate = useSWRConfig().mutate;
|
||||
const api = useApi();
|
||||
|
||||
return async (arg?: string | ((api: Api) => FetchPromise<any>)) => {
|
||||
if (!arg) return async () => mutate("");
|
||||
return mutate(typeof arg === "string" ? arg : arg(api).key());
|
||||
};
|
||||
};
|
||||
|
||||
@@ -100,6 +100,7 @@ export const useEntityQuery = <
|
||||
return {
|
||||
...swr,
|
||||
...mapped,
|
||||
api,
|
||||
key
|
||||
};
|
||||
};
|
||||
|
||||
@@ -2,7 +2,7 @@ export {
|
||||
ClientProvider,
|
||||
useBkndWindowContext,
|
||||
type ClientProviderProps,
|
||||
useClient,
|
||||
useApi,
|
||||
useBaseUrl
|
||||
} from "./ClientProvider";
|
||||
|
||||
|
||||
@@ -1,21 +1,19 @@
|
||||
import { type NotificationData, notifications } from "@mantine/notifications";
|
||||
import type { Api } from "Api";
|
||||
import { ucFirst } from "core/utils";
|
||||
import type { ModuleConfigs } from "modules";
|
||||
import type { ResponseObject } from "modules/ModuleApi";
|
||||
import type { ConfigUpdateResponse } from "modules/server/SystemController";
|
||||
import type { AppQueryClient } from "../utils/AppQueryClient";
|
||||
|
||||
export type SchemaActionsProps = {
|
||||
client: AppQueryClient;
|
||||
api: Api;
|
||||
setSchema: React.Dispatch<React.SetStateAction<any>>;
|
||||
reloadSchema: () => Promise<void>;
|
||||
};
|
||||
|
||||
export type TSchemaActions = ReturnType<typeof getSchemaActions>;
|
||||
|
||||
export function getSchemaActions({ client, setSchema, reloadSchema }: SchemaActionsProps) {
|
||||
const api = client.api;
|
||||
|
||||
export function getSchemaActions({ api, setSchema, reloadSchema }: SchemaActionsProps) {
|
||||
async function handleConfigUpdate<Module extends keyof ModuleConfigs>(
|
||||
action: string,
|
||||
module: Module,
|
||||
|
||||
@@ -1,15 +1,8 @@
|
||||
import { Api } from "Api";
|
||||
import { Api, type AuthState } from "Api";
|
||||
import type { AuthResponse } from "auth";
|
||||
import type { AppAuthSchema } from "auth/auth-schema";
|
||||
import type { ApiResponse } from "modules/ModuleApi";
|
||||
import { useEffect, useState } from "react";
|
||||
import {
|
||||
createClient,
|
||||
createOrUseClient,
|
||||
queryClient,
|
||||
useBaseUrl,
|
||||
useClient
|
||||
} from "../../ClientProvider";
|
||||
import { useApi, useInvalidate } from "ui/client";
|
||||
|
||||
type LoginData = {
|
||||
email: string;
|
||||
@@ -18,55 +11,54 @@ type LoginData = {
|
||||
};
|
||||
|
||||
type UseAuth = {
|
||||
data: (AuthResponse & { verified: boolean }) | undefined;
|
||||
user: AuthResponse["user"] | undefined;
|
||||
token: AuthResponse["token"] | undefined;
|
||||
data: AuthState | undefined;
|
||||
user: AuthState["user"] | undefined;
|
||||
token: AuthState["token"] | undefined;
|
||||
verified: boolean;
|
||||
login: (data: LoginData) => Promise<ApiResponse<AuthResponse>>;
|
||||
register: (data: LoginData) => Promise<ApiResponse<AuthResponse>>;
|
||||
login: (data: LoginData) => Promise<AuthResponse>;
|
||||
register: (data: LoginData) => Promise<AuthResponse>;
|
||||
logout: () => void;
|
||||
verify: () => void;
|
||||
setToken: (token: string) => void;
|
||||
};
|
||||
|
||||
// @todo: needs to use a specific auth endpoint to get strategy information
|
||||
export const useAuth = (options?: { baseUrl?: string }): UseAuth => {
|
||||
const ctxBaseUrl = useBaseUrl();
|
||||
//const client = useClient();
|
||||
const client = createOrUseClient(options?.baseUrl ? options?.baseUrl : ctxBaseUrl);
|
||||
const authState = client.auth().state();
|
||||
const api = useApi(options?.baseUrl);
|
||||
const invalidate = useInvalidate();
|
||||
const authState = api.getAuthState();
|
||||
const [authData, setAuthData] = useState<UseAuth["data"]>(authState);
|
||||
const verified = authState?.verified ?? false;
|
||||
|
||||
function updateAuthState() {
|
||||
setAuthData(api.getAuthState());
|
||||
}
|
||||
|
||||
async function login(input: LoginData) {
|
||||
const res = await client.auth().login(input);
|
||||
if (res.res.ok && res.data && "user" in res.data) {
|
||||
setAuthData(res.data);
|
||||
}
|
||||
return res;
|
||||
const res = await api.auth.loginWithPassword(input);
|
||||
updateAuthState();
|
||||
return res.data;
|
||||
}
|
||||
|
||||
async function register(input: LoginData) {
|
||||
const res = await client.auth().register(input);
|
||||
if (res.res.ok && res.data && "user" in res.data) {
|
||||
setAuthData(res.data);
|
||||
}
|
||||
return res;
|
||||
const res = await api.auth.registerWithPassword(input);
|
||||
updateAuthState();
|
||||
return res.data;
|
||||
}
|
||||
|
||||
function setToken(token: string) {
|
||||
setAuthData(client.auth().setToken(token) as any);
|
||||
api.updateToken(token);
|
||||
updateAuthState();
|
||||
}
|
||||
|
||||
async function logout() {
|
||||
await client.auth().logout();
|
||||
await api.updateToken(undefined);
|
||||
setAuthData(undefined);
|
||||
queryClient.clear();
|
||||
invalidate();
|
||||
}
|
||||
|
||||
async function verify() {
|
||||
await client.auth().verify();
|
||||
setAuthData(client.auth().state());
|
||||
await api.verifyAuth();
|
||||
updateAuthState();
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -87,10 +79,7 @@ export const useAuthStrategies = (options?: { baseUrl?: string }): Partial<AuthS
|
||||
loading: boolean;
|
||||
} => {
|
||||
const [data, setData] = useState<AuthStrategyData>();
|
||||
const ctxBaseUrl = useBaseUrl();
|
||||
const api = new Api({
|
||||
host: options?.baseUrl ? options?.baseUrl : ctxBaseUrl
|
||||
});
|
||||
const api = useApi(options?.baseUrl);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { useBknd } from "ui/client/bknd";
|
||||
|
||||
export function useBkndAuth() {
|
||||
//const client = useClient();
|
||||
const { config, app, schema, actions: bkndActions } = useBknd();
|
||||
const { config, schema, actions: bkndActions } = useBknd();
|
||||
|
||||
const actions = {
|
||||
roles: {
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import { type Static, parse } from "core/utils";
|
||||
import { type TAppFlowSchema, flowSchema } from "flows/flows-schema";
|
||||
import { useBknd } from "../../BkndProvider";
|
||||
import { useClient } from "../../ClientProvider";
|
||||
|
||||
export function useFlows() {
|
||||
const client = useClient();
|
||||
const { config, app, actions: bkndActions } = useBknd();
|
||||
|
||||
const actions = {
|
||||
|
||||
@@ -1,216 +0,0 @@
|
||||
import {
|
||||
type QueryObserverOptions,
|
||||
type UseQueryResult,
|
||||
keepPreviousData,
|
||||
useMutation,
|
||||
useQuery
|
||||
} from "@tanstack/react-query";
|
||||
import type { AuthResponse } from "auth";
|
||||
import type { EntityData, RepoQuery, RepositoryResponse } from "data";
|
||||
import { Api } from "../../../Api";
|
||||
import type { ApiResponse } from "../../../modules/ModuleApi";
|
||||
import { queryClient } from "../ClientProvider";
|
||||
|
||||
export class AppQueryClient {
|
||||
api: Api;
|
||||
constructor(
|
||||
public baseUrl: string,
|
||||
user?: object
|
||||
) {
|
||||
this.api = new Api({
|
||||
host: baseUrl,
|
||||
user
|
||||
});
|
||||
}
|
||||
|
||||
queryOptions(options?: Partial<QueryObserverOptions>): Partial<QueryObserverOptions> {
|
||||
return {
|
||||
staleTime: 1000 * 60 * 5,
|
||||
placeholderData: keepPreviousData,
|
||||
...options
|
||||
};
|
||||
}
|
||||
|
||||
auth = () => {
|
||||
return {
|
||||
state: (): (AuthResponse & { verified: boolean }) | undefined => {
|
||||
return this.api.getAuthState() as any;
|
||||
},
|
||||
login: async (data: { email: string; password: string }) => {
|
||||
return await this.api.auth.loginWithPassword(data);
|
||||
},
|
||||
register: async (data: any) => {
|
||||
return await this.api.auth.registerWithPassword(data);
|
||||
},
|
||||
logout: async () => {
|
||||
this.api.updateToken(undefined);
|
||||
return true;
|
||||
},
|
||||
setToken: (token) => {
|
||||
this.api.updateToken(token);
|
||||
return this.api.getAuthState();
|
||||
},
|
||||
verify: async () => {
|
||||
try {
|
||||
//console.log("verifiying");
|
||||
const res = await this.api.auth.me();
|
||||
//console.log("verifying result", res);
|
||||
if (!res.ok || !res.body.user) {
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
this.api.markAuthVerified(true);
|
||||
} catch (e) {
|
||||
this.api.markAuthVerified(false);
|
||||
this.api.updateToken(undefined);
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
media = (options?: Partial<QueryObserverOptions>) => {
|
||||
const queryOptions = this.queryOptions(options);
|
||||
return {
|
||||
api: () => {
|
||||
return this.api.media;
|
||||
},
|
||||
list: (query: Partial<RepoQuery> = { limit: 10 }): UseQueryResult<ApiResponse> => {
|
||||
return useQuery({
|
||||
...(queryOptions as any), // @todo: fix typing
|
||||
queryKey: ["data", "entity", "media", { query }],
|
||||
queryFn: async () => {
|
||||
return await this.api.data.readMany("media", query);
|
||||
}
|
||||
});
|
||||
},
|
||||
deleteFile: async (filename: string | { path: string }) => {
|
||||
const res = await this.api.media.deleteFile(
|
||||
typeof filename === "string" ? filename : filename.path
|
||||
);
|
||||
|
||||
if (res.ok) {
|
||||
queryClient.invalidateQueries({ queryKey: ["data", "entity", "media"] });
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
query = (options?: Partial<QueryObserverOptions>) => {
|
||||
const queryOptions = this.queryOptions(options);
|
||||
return {
|
||||
data: {
|
||||
entity: (name: string) => {
|
||||
return {
|
||||
readOne: (
|
||||
id: number,
|
||||
query: Partial<Omit<RepoQuery, "where" | "limit" | "offset">> = {}
|
||||
): any => {
|
||||
return useQuery({
|
||||
...queryOptions,
|
||||
queryKey: ["data", "entity", name, id, { query }],
|
||||
queryFn: async () => {
|
||||
return await this.api.data.readOne(name, id, query);
|
||||
}
|
||||
});
|
||||
},
|
||||
readMany: (
|
||||
query: Partial<RepoQuery> = { limit: 10, offset: 0 }
|
||||
): UseQueryResult<ApiResponse> => {
|
||||
return useQuery({
|
||||
...(queryOptions as any), // @todo: fix typing
|
||||
queryKey: ["data", "entity", name, { query }],
|
||||
queryFn: async () => {
|
||||
return await this.api.data.readMany(name, query);
|
||||
}
|
||||
});
|
||||
},
|
||||
readManyByReference: (
|
||||
id: number,
|
||||
reference: string,
|
||||
referenced_entity?: string, // required for query invalidation
|
||||
query: Partial<RepoQuery> = { limit: 10, offset: 0 }
|
||||
): UseQueryResult<Pick<RepositoryResponse, "meta" | "data">> => {
|
||||
return useQuery({
|
||||
...(queryOptions as any), // @todo: fix typing
|
||||
queryKey: [
|
||||
"data",
|
||||
"entity",
|
||||
referenced_entity ?? reference,
|
||||
{ name, id, reference, query }
|
||||
],
|
||||
queryFn: async () => {
|
||||
return await this.api.data.readManyByReference(
|
||||
name,
|
||||
id,
|
||||
reference,
|
||||
query
|
||||
);
|
||||
}
|
||||
});
|
||||
},
|
||||
count: (
|
||||
where: RepoQuery["where"] = {}
|
||||
): UseQueryResult<ApiResponse<{ entity: string; count: number }>> => {
|
||||
return useQuery({
|
||||
...(queryOptions as any), // @todo: fix typing
|
||||
queryKey: ["data", "entity", name, "fn", "count", { where }],
|
||||
queryFn: async () => {
|
||||
return await this.api.data.count(name, where);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// @todo: centralize, improve
|
||||
__invalidate = (...args: any[]) => {
|
||||
console.log("___invalidate", ["data", "entity", ...args]);
|
||||
queryClient.invalidateQueries({ queryKey: ["data", "entity", ...args] });
|
||||
};
|
||||
|
||||
// @todo: must return response... why?
|
||||
mutation = {
|
||||
data: {
|
||||
entity: (name: string) => {
|
||||
return {
|
||||
update: (id: number): any => {
|
||||
return useMutation({
|
||||
mutationFn: async (input: EntityData) => {
|
||||
return await this.api.data.updateOne(name, id, input);
|
||||
},
|
||||
onSuccess: async () => {
|
||||
await queryClient.invalidateQueries({ queryKey: ["data", "entity", name] });
|
||||
}
|
||||
});
|
||||
},
|
||||
create: (): any => {
|
||||
return useMutation({
|
||||
mutationFn: async (input: EntityData) => {
|
||||
return await this.api.data.createOne(name, input);
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ["data", "entity", name] });
|
||||
}
|
||||
});
|
||||
},
|
||||
delete: (id: number): any => {
|
||||
return useMutation({
|
||||
mutationFn: async () => {
|
||||
return await this.api.data.deleteOne(name, id);
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ["data", "entity", name] });
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user