mirror of
https://github.com/shishantbiswas/bknd.git
synced 2026-03-16 04:27:21 +00:00
added useApi and useApiQuery to work seamlessly with SWR (tbc)
This commit is contained in:
@@ -22,18 +22,19 @@
|
|||||||
},
|
},
|
||||||
"license": "FSL-1.1-MIT",
|
"license": "FSL-1.1-MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@cfworker/json-schema": "^2.0.1",
|
||||||
"@libsql/client": "^0.14.0",
|
"@libsql/client": "^0.14.0",
|
||||||
"@tanstack/react-form": "0.19.2",
|
|
||||||
"@sinclair/typebox": "^0.32.34",
|
"@sinclair/typebox": "^0.32.34",
|
||||||
|
"@tanstack/react-form": "0.19.2",
|
||||||
|
"aws4fetch": "^1.0.18",
|
||||||
|
"dayjs": "^1.11.13",
|
||||||
|
"fast-xml-parser": "^4.4.0",
|
||||||
|
"hono": "^4.6.12",
|
||||||
"kysely": "^0.27.4",
|
"kysely": "^0.27.4",
|
||||||
"liquidjs": "^10.15.0",
|
"liquidjs": "^10.15.0",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
"hono": "^4.6.12",
|
|
||||||
"fast-xml-parser": "^4.4.0",
|
|
||||||
"@cfworker/json-schema": "^2.0.1",
|
|
||||||
"dayjs": "^1.11.13",
|
|
||||||
"oauth4webapi": "^2.11.1",
|
"oauth4webapi": "^2.11.1",
|
||||||
"aws4fetch": "^1.0.18"
|
"swr": "^2.2.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@aws-sdk/client-s3": "^3.613.0",
|
"@aws-sdk/client-s3": "^3.613.0",
|
||||||
|
|||||||
@@ -29,11 +29,11 @@ export class AuthApi extends ModuleApi<AuthApiOptions> {
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
async me() {
|
me() {
|
||||||
return this.get<{ user: SafeUser | null }>(["me"]);
|
return this.get<{ user: SafeUser | null }>(["me"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
async strategies() {
|
strategies() {
|
||||||
return this.get<Pick<AppAuthSchema, "strategies" | "basepath">>(["strategies"]);
|
return this.get<Pick<AppAuthSchema, "strategies" | "basepath">>(["strategies"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ export class DataApi extends ModuleApi<DataApiOptions> {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async readOne(
|
readOne(
|
||||||
entity: string,
|
entity: string,
|
||||||
id: PrimaryFieldType,
|
id: PrimaryFieldType,
|
||||||
query: Partial<Omit<RepoQuery, "where" | "limit" | "offset">> = {}
|
query: Partial<Omit<RepoQuery, "where" | "limit" | "offset">> = {}
|
||||||
@@ -23,14 +23,14 @@ export class DataApi extends ModuleApi<DataApiOptions> {
|
|||||||
return this.get<RepositoryResponse<EntityData>>([entity, id], query);
|
return this.get<RepositoryResponse<EntityData>>([entity, id], query);
|
||||||
}
|
}
|
||||||
|
|
||||||
async readMany(entity: string, query: Partial<RepoQuery> = {}) {
|
readMany(entity: string, query: Partial<RepoQuery> = {}) {
|
||||||
return this.get<Pick<RepositoryResponse, "meta" | "data">>(
|
return this.get<Pick<RepositoryResponse, "meta" | "data">>(
|
||||||
[entity],
|
[entity],
|
||||||
query ?? this.options.defaultQuery
|
query ?? this.options.defaultQuery
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async readManyByReference(
|
readManyByReference(
|
||||||
entity: string,
|
entity: string,
|
||||||
id: PrimaryFieldType,
|
id: PrimaryFieldType,
|
||||||
reference: string,
|
reference: string,
|
||||||
@@ -42,19 +42,19 @@ export class DataApi extends ModuleApi<DataApiOptions> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async createOne(entity: string, input: EntityData) {
|
createOne(entity: string, input: EntityData) {
|
||||||
return this.post<RepositoryResponse<EntityData>>([entity], input);
|
return this.post<RepositoryResponse<EntityData>>([entity], input);
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateOne(entity: string, id: PrimaryFieldType, input: EntityData) {
|
updateOne(entity: string, id: PrimaryFieldType, input: EntityData) {
|
||||||
return this.patch<RepositoryResponse<EntityData>>([entity, id], input);
|
return this.patch<RepositoryResponse<EntityData>>([entity, id], input);
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteOne(entity: string, id: PrimaryFieldType) {
|
deleteOne(entity: string, id: PrimaryFieldType) {
|
||||||
return this.delete<RepositoryResponse<EntityData>>([entity, id]);
|
return this.delete<RepositoryResponse<EntityData>>([entity, id]);
|
||||||
}
|
}
|
||||||
|
|
||||||
async count(entity: string, where: RepoQuery["where"] = {}) {
|
count(entity: string, where: RepoQuery["where"] = {}) {
|
||||||
return this.post<RepositoryResponse<{ entity: string; count: number }>>(
|
return this.post<RepositoryResponse<{ entity: string; count: number }>>(
|
||||||
[entity, "fn", "count"],
|
[entity, "fn", "count"],
|
||||||
where
|
where
|
||||||
|
|||||||
@@ -10,11 +10,11 @@ export class MediaApi extends ModuleApi<MediaApiOptions> {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async getFiles() {
|
getFiles() {
|
||||||
return this.get(["files"]);
|
return this.get(["files"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getFile(filename: string) {
|
getFile(filename: string) {
|
||||||
return this.get(["file", filename]);
|
return this.get(["file", filename]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,13 +32,13 @@ export class MediaApi extends ModuleApi<MediaApiOptions> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async uploadFile(file: File) {
|
uploadFile(file: File) {
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append("file", file);
|
formData.append("file", file);
|
||||||
return this.post(["upload"], formData);
|
return this.post(["upload"], formData);
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteFile(filename: string) {
|
deleteFile(filename: string) {
|
||||||
return this.delete(["file", filename]);
|
return this.delete(["file", filename]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ export type ApiResponse<Data = any> = {
|
|||||||
export type TInput = string | (string | number | PrimaryFieldType)[];
|
export type TInput = string | (string | number | PrimaryFieldType)[];
|
||||||
|
|
||||||
export abstract class ModuleApi<Options extends BaseModuleApiOptions = BaseModuleApiOptions> {
|
export abstract class ModuleApi<Options extends BaseModuleApiOptions = BaseModuleApiOptions> {
|
||||||
fetcher = fetch;
|
protected fetcher?: typeof fetch;
|
||||||
|
|
||||||
constructor(protected readonly _options: Partial<Options> = {}) {}
|
constructor(protected readonly _options: Partial<Options> = {}) {}
|
||||||
|
|
||||||
@@ -136,17 +136,18 @@ export abstract class ModuleApi<Options extends BaseModuleApiOptions = BaseModul
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class FetchPromise<T> implements Promise<T> {
|
export class FetchPromise<T = ApiResponse<any>> implements Promise<T> {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
[Symbol.toStringTag]: "FetchPromise";
|
[Symbol.toStringTag]: "FetchPromise";
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public request: Request,
|
public request: Request,
|
||||||
protected fetcher = fetch
|
protected fetcher?: typeof fetch
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async execute() {
|
async execute() {
|
||||||
const res = await this.fetcher(this.request);
|
const fetcher = this.fetcher ?? fetch;
|
||||||
|
const res = await fetcher(this.request);
|
||||||
let resBody: any;
|
let resBody: any;
|
||||||
let resData: any;
|
let resData: any;
|
||||||
|
|
||||||
@@ -195,4 +196,9 @@ class FetchPromise<T> implements Promise<T> {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getKey(): string {
|
||||||
|
const url = new URL(this.request.url);
|
||||||
|
return url.pathname + url.search;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,5 +6,6 @@ export {
|
|||||||
useBaseUrl
|
useBaseUrl
|
||||||
} from "./ClientProvider";
|
} from "./ClientProvider";
|
||||||
|
|
||||||
|
export { useApi, useApiQuery } from "./use-api";
|
||||||
export { useAuth } from "./schema/auth/use-auth";
|
export { useAuth } from "./schema/auth/use-auth";
|
||||||
export { Api } from "../../Api";
|
export { Api } from "../../Api";
|
||||||
|
|||||||
18
app/src/ui/client/use-api.ts
Normal file
18
app/src/ui/client/use-api.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
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);
|
||||||
|
};
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import AppShellAccordionsTest from "ui/routes/test/tests/appshell-accordions-test";
|
import AppShellAccordionsTest from "ui/routes/test/tests/appshell-accordions-test";
|
||||||
import SwaggerTest from "ui/routes/test/tests/swagger-test";
|
import SwaggerTest from "ui/routes/test/tests/swagger-test";
|
||||||
|
import SWRAndAPI from "ui/routes/test/tests/swr-and-api";
|
||||||
import { Route, useParams } from "wouter";
|
import { Route, useParams } from "wouter";
|
||||||
import { Empty } from "../../components/display/Empty";
|
import { Empty } from "../../components/display/Empty";
|
||||||
import { Link } from "../../components/wouter/Link";
|
import { Link } from "../../components/wouter/Link";
|
||||||
@@ -37,7 +38,8 @@ const tests = {
|
|||||||
EntityFieldsForm,
|
EntityFieldsForm,
|
||||||
FlowsTest,
|
FlowsTest,
|
||||||
AppShellAccordionsTest,
|
AppShellAccordionsTest,
|
||||||
SwaggerTest
|
SwaggerTest,
|
||||||
|
SWRAndAPI
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export default function TestRoutes() {
|
export default function TestRoutes() {
|
||||||
|
|||||||
20
app/src/ui/routes/test/tests/swr-and-api.tsx
Normal file
20
app/src/ui/routes/test/tests/swr-and-api.tsx
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import { useApiQuery } from "ui/client";
|
||||||
|
import { Scrollable } from "ui/layouts/AppShell/AppShell";
|
||||||
|
|
||||||
|
export default function SWRAndAPI() {
|
||||||
|
const [enabled, setEnabled] = useState(false);
|
||||||
|
const { data, error, isLoading } = useApiQuery(({ data }) => data.readMany("posts"), {
|
||||||
|
enabled,
|
||||||
|
revalidateOnFocus: true
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Scrollable>
|
||||||
|
<button onClick={() => setEnabled((p) => !p)}>{enabled ? "disable" : "enable"}</button>
|
||||||
|
{error && <div>failed to load</div>}
|
||||||
|
{isLoading && <div>loading...</div>}
|
||||||
|
{data && <pre>{JSON.stringify(data, null, 2)}</pre>}
|
||||||
|
</Scrollable>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user