added useEntity and useEntityQuery hooks

This commit is contained in:
dswbx
2024-12-12 17:00:10 +01:00
parent 9d9aa7b7a5
commit 50c5adce0c
13 changed files with 456 additions and 38 deletions

View File

@@ -80,8 +80,6 @@ export const useClient = () => {
if (!context) {
throw new Error("useClient must be used within a ClientProvider");
}
console.log("useClient", context.baseUrl);
return context.client;
};

View File

@@ -0,0 +1,30 @@
import type { Api } from "Api";
import type { FetchPromise } from "modules/ModuleApi";
import useSWR, { type SWRConfiguration, useSWRConfig } from "swr";
import { useClient } from "ui/client/ClientProvider";
export const useApi = () => {
const client = useClient();
return client.api;
};
export const useApiQuery = <Data, RefineFn extends (data: Data) => any = (data: Data) => Data>(
fn: (api: Api) => FetchPromise<Data>,
options?: SWRConfiguration & { enabled?: boolean; refine?: RefineFn }
) => {
const api = useApi();
const promise = fn(api);
const refine = options?.refine ?? ((data: Data) => data);
const fetcher = () => promise.execute().then(refine);
const key = promise.key();
type RefinedData = RefineFn extends (data: Data) => infer R ? R : Data;
const swr = useSWR<RefinedData>(options?.enabled === false ? null : key, fetcher, options);
return {
...swr,
promise,
key,
api
};
};

View File

@@ -0,0 +1,37 @@
import type { DataApi } from "data/api/DataApi";
import { useApi } from "ui/client";
type OmitFirstArg<F> = F extends (x: any, ...args: infer P) => any
? (...args: P) => ReturnType<F>
: never;
/**
* Maps all DataApi functions and omits
* the first argument "entity" for convenience
* @param entity
*/
export const useData = <T extends keyof DataApi>(entity: string) => {
const api = useApi().data;
const methods = [
"readOne",
"readMany",
"readManyByReference",
"createOne",
"updateOne",
"deleteOne"
] as const;
return methods.reduce(
(acc, method) => {
// @ts-ignore
acc[method] = (...params) => {
// @ts-ignore
return api[method](entity, ...params);
};
return acc;
},
{} as {
[K in (typeof methods)[number]]: OmitFirstArg<(typeof api)[K]>;
}
);
};

View File

@@ -0,0 +1,76 @@
import type { PrimaryFieldType } from "core";
import { objectTransform } from "core/utils";
import type { EntityData, RepoQuery } from "data";
import useSWR, { type SWRConfiguration } from "swr";
import { useApi } from "ui/client";
export const useEntity = <
Entity extends string,
Id extends PrimaryFieldType | undefined = undefined
>(
entity: Entity,
id?: Id
) => {
const api = useApi().data;
return {
create: async (input: EntityData) => {
const res = await api.createOne(entity, input);
return res.data;
},
read: async (query: Partial<RepoQuery> = {}) => {
const res = id ? await api.readOne(entity, id!, query) : await api.readMany(entity, query);
return res.data;
},
update: async (input: Partial<EntityData>, _id: PrimaryFieldType | undefined = id) => {
if (!_id) {
throw new Error("id is required");
}
const res = await api.updateOne(entity, _id, input);
return res.data;
},
_delete: async (_id: PrimaryFieldType | undefined = id) => {
if (!_id) {
throw new Error("id is required");
}
const res = await api.deleteOne(entity, _id);
return res.data;
}
};
};
export const useEntityQuery = <
Entity extends string,
Id extends PrimaryFieldType | undefined = undefined
>(
entity: Entity,
id?: Id,
query?: Partial<RepoQuery>,
options?: SWRConfiguration
) => {
const api = useApi().data;
const key = [...(api.options?.basepath?.split("/") ?? []), entity, ...(id ? [id] : [])].filter(
Boolean
);
const { read, ...actions } = useEntity(entity, id) as any;
const fetcher = id ? () => read(query) : () => null;
const swr = useSWR<EntityData>(id ? key : null, fetcher, options);
const mapped = objectTransform(actions, (action) => {
if (action === "read") return;
return async (...args) => {
return swr.mutate(async () => {
const res = await action(...args);
return res;
});
};
}) as Omit<ReturnType<typeof useEntity<Entity, Id>>, "read">;
return {
...swr,
...mapped,
key
};
};

View File

@@ -6,6 +6,8 @@ export {
useBaseUrl
} from "./ClientProvider";
export { useApi, useApiQuery } from "./use-api";
export * from "./api/use-api";
export * from "./api/use-data";
export * from "./api/use-entity";
export { useAuth } from "./schema/auth/use-auth";
export { Api } from "../../Api";

View File

@@ -1,18 +0,0 @@
import type { Api } from "Api";
import type { FetchPromise } from "modules/ModuleApi";
import useSWR, { type SWRConfiguration } from "swr";
import { useClient } from "ui/client/ClientProvider";
export const useApi = () => {
const client = useClient();
return client.api;
};
export const useApiQuery = <T = any>(
fn: (api: Api) => FetchPromise<T>,
options?: SWRConfiguration & { enabled?: boolean }
) => {
const api = useApi();
const p = fn(api);
return useSWR<T>(options?.enabled === false ? null : p.getKey(), () => p, options);
};