optimized module manager seeding, added type support for new api hooks and reduced amount of dist chunks

This commit is contained in:
dswbx
2024-12-18 18:22:01 +01:00
parent c4138ef823
commit 602235b372
41 changed files with 434 additions and 328 deletions

View File

@@ -5,14 +5,14 @@ import { useApi } from "ui/client";
export const useApiQuery = <
Data,
RefineFn extends (data: ResponseObject<Data>) => any = (data: ResponseObject<Data>) => Data
RefineFn extends (data: ResponseObject<Data>) => unknown = (data: ResponseObject<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: ResponseObject<Data>) => data);
const refine = options?.refine ?? ((data: any) => data);
const fetcher = () => promise.execute().then(refine);
const key = promise.key();

View File

@@ -1,8 +1,8 @@
import type { PrimaryFieldType } from "core";
import { objectTransform } from "core/utils";
import type { EntityData, RepoQuery } from "data";
import type { ResponseObject } from "modules/ModuleApi";
import useSWR, { type SWRConfiguration } from "swr";
import type { ModuleApi, ResponseObject } from "modules/ModuleApi";
import useSWR, { type SWRConfiguration, useSWRConfig } from "swr";
import { useApi } from "ui/client";
export class UseEntityApiError<Payload = any> extends Error {
@@ -15,9 +15,19 @@ export class UseEntityApiError<Payload = any> extends Error {
}
}
function Test() {
const { read } = useEntity("users");
async () => {
const data = await read();
};
return null;
}
export const useEntity = <
Entity extends string,
Id extends PrimaryFieldType | undefined = undefined
Entity extends keyof DB | string,
Id extends PrimaryFieldType | undefined = undefined,
Data = Entity extends keyof DB ? DB[Entity] : EntityData
>(
entity: Entity,
id?: Id
@@ -25,7 +35,7 @@ export const useEntity = <
const api = useApi().data;
return {
create: async (input: EntityData) => {
create: async (input: Omit<Data, "id">) => {
const res = await api.createOne(entity, input);
if (!res.ok) {
throw new UseEntityApiError(res.data, res.res, "Failed to create entity");
@@ -37,9 +47,12 @@ export const useEntity = <
if (!res.ok) {
throw new UseEntityApiError(res.data, res.res, "Failed to read entity");
}
return res;
// must be manually typed
return res as unknown as Id extends undefined
? ResponseObject<Data[]>
: ResponseObject<Data>;
},
update: async (input: Partial<EntityData>, _id: PrimaryFieldType | undefined = id) => {
update: async (input: Partial<Omit<Data, "id">>, _id: PrimaryFieldType | undefined = id) => {
if (!_id) {
throw new Error("id is required");
}
@@ -63,8 +76,17 @@ export const useEntity = <
};
};
export function makeKey(api: ModuleApi, entity: string, id?: PrimaryFieldType) {
return (
"/" +
[...(api.options?.basepath?.split("/") ?? []), entity, ...(id ? [id] : [])]
.filter(Boolean)
.join("/")
);
}
export const useEntityQuery = <
Entity extends string,
Entity extends keyof DB | string,
Id extends PrimaryFieldType | undefined = undefined
>(
entity: Entity,
@@ -72,28 +94,28 @@ export const useEntityQuery = <
query?: Partial<RepoQuery>,
options?: SWRConfiguration & { enabled?: boolean }
) => {
const { mutate } = useSWRConfig();
const api = useApi().data;
const key =
options?.enabled !== false
? [...(api.options?.basepath?.split("/") ?? []), entity, ...(id ? [id] : [])].filter(
Boolean
)
: null;
const { read, ...actions } = useEntity(entity, id) as any;
const key = makeKey(api, entity, id);
const { read, ...actions } = useEntity<Entity, Id>(entity, id);
const fetcher = () => read(query);
type T = Awaited<ReturnType<(typeof api)[Id extends undefined ? "readMany" : "readOne"]>>;
const swr = useSWR<T>(key, fetcher, {
type T = Awaited<ReturnType<typeof fetcher>>;
const swr = useSWR<T>(options?.enabled === false ? null : key, fetcher as any, {
revalidateOnFocus: false,
keepPreviousData: false,
...options
});
const mapped = objectTransform(actions, (action) => {
if (action === "read") return;
return async (...args: any) => {
// @ts-ignore
const res = await action(...args);
return async (...args) => {
return swr.mutate(action(...args)) as any;
// mutate the key + list key
mutate(key);
if (id) mutate(makeKey(api, entity));
return res;
};
}) as Omit<ReturnType<typeof useEntity<Entity, Id>>, "read">;
@@ -106,14 +128,14 @@ export const useEntityQuery = <
};
export const useEntityMutate = <
Entity extends string,
Entity extends keyof DB | string,
Id extends PrimaryFieldType | undefined = undefined
>(
entity: Entity,
id?: Id,
options?: SWRConfiguration
) => {
const { data, ...$q } = useEntityQuery(entity, id, undefined, {
const { data, ...$q } = useEntityQuery<Entity, Id>(entity, id, undefined, {
...options,
enabled: false
});

View File

@@ -1,7 +1,6 @@
import type { ReactCodeMirrorProps } from "@uiw/react-codemirror";
import { Suspense, lazy } from "react";
import { default as CodeMirror, type ReactCodeMirrorProps } from "@uiw/react-codemirror";
import { useBknd } from "ui/client/bknd";
const CodeMirror = lazy(() => import("@uiw/react-codemirror"));
export default function CodeEditor({ editable, basicSetup, ...props }: ReactCodeMirrorProps) {
const b = useBknd();
@@ -15,13 +14,11 @@ export default function CodeEditor({ editable, basicSetup, ...props }: ReactCode
: basicSetup;
return (
<Suspense>
<CodeMirror
theme={theme === "dark" ? "dark" : "light"}
editable={editable}
basicSetup={_basicSetup}
{...props}
/>
</Suspense>
<CodeMirror
theme={theme === "dark" ? "dark" : "light"}
editable={editable}
basicSetup={_basicSetup}
{...props}
/>
);
}

View File

@@ -1,15 +1,12 @@
import type { Schema } from "@cfworker/json-schema";
import Form from "@rjsf/core";
import type { RJSFSchema, UiSchema } from "@rjsf/utils";
import { cloneDeep } from "lodash-es";
import { forwardRef, useId, useImperativeHandle, useRef, useState } from "react";
//import { JsonSchemaValidator } from "./JsonSchemaValidator";
import { fields as Fields } from "./fields";
import { templates as Templates } from "./templates";
import { widgets as Widgets } from "./widgets";
import "./styles.css";
import { filterKeys } from "core/utils";
import { cloneDeep } from "lodash-es";
import { RJSFTypeboxValidator } from "./typebox/RJSFTypeboxValidator";
import { widgets as Widgets } from "./widgets";
const validator = new RJSFTypeboxValidator();

View File

@@ -0,0 +1,18 @@
import { Suspense, forwardRef, lazy } from "react";
import type { JsonSchemaFormProps, JsonSchemaFormRef } from "./JsonSchemaForm";
export type { JsonSchemaFormProps, JsonSchemaFormRef };
const Module = lazy(() =>
import("./JsonSchemaForm").then((m) => ({
default: m.JsonSchemaForm
}))
);
export const JsonSchemaForm = forwardRef<JsonSchemaFormRef, JsonSchemaFormProps>((props, ref) => {
return (
<Suspense>
<Module ref={ref} {...props} />
</Suspense>
);
});

View File

@@ -4,7 +4,7 @@ import {
JsonSchemaForm,
type JsonSchemaFormProps,
type JsonSchemaFormRef
} from "ui/components/form/json-schema/JsonSchemaForm";
} from "ui/components/form/json-schema";
import type { ContextModalProps } from "@mantine/modals";

View File

@@ -1,14 +1,8 @@
import type { FieldApi } from "@tanstack/react-form";
import type { EntityData, JsonSchemaField } from "data";
import { Suspense, lazy } from "react";
import * as Formy from "ui/components/form/Formy";
import { FieldLabel } from "ui/components/form/Formy";
const JsonSchemaForm = lazy(() =>
import("ui/components/form/json-schema/JsonSchemaForm").then((m) => ({
default: m.JsonSchemaForm
}))
);
import { JsonSchemaForm } from "ui/components/form/json-schema";
export function EntityJsonSchemaFormField({
fieldApi,
@@ -34,23 +28,21 @@ export function EntityJsonSchemaFormField({
return (
<Formy.Group>
<FieldLabel htmlFor={fieldApi.name} field={field} />
<Suspense fallback={<div>Loading...</div>}>
<div
data-disabled={disabled ? 1 : undefined}
className="data-[disabled]:opacity-70 data-[disabled]:pointer-events-none"
>
<JsonSchemaForm
schema={field.getJsonSchema()}
onChange={handleChange}
direction="horizontal"
formData={formData}
uiSchema={{
"ui:globalOptions": { flexDirection: "row" },
...field.getJsonUiSchema()
}}
/>
</div>
</Suspense>
<div
data-disabled={disabled ? 1 : undefined}
className="data-[disabled]:opacity-70 data-[disabled]:pointer-events-none"
>
<JsonSchemaForm
schema={field.getJsonSchema()}
onChange={handleChange}
direction="horizontal"
formData={formData}
uiSchema={{
"ui:globalOptions": { flexDirection: "row" },
...field.getJsonUiSchema()
}}
/>
</div>
</Formy.Group>
);
}

View File

@@ -1,14 +1,9 @@
import { Handle, type Node, type NodeProps, Position } from "@xyflow/react";
import { Const, Type, transformObject } from "core/utils";
import { type TaskRenderProps, type Trigger, TriggerMap } from "flows";
import { Suspense, lazy } from "react";
import { type Trigger, TriggerMap } from "flows";
import type { IconType } from "react-icons";
import { TbCircleLetterT } from "react-icons/tb";
const JsonSchemaForm = lazy(() =>
import("ui/components/form/json-schema/JsonSchemaForm").then((m) => ({
default: m.JsonSchemaForm
}))
);
import { JsonSchemaForm } from "ui/components/form/json-schema";
export type TaskComponentProps = NodeProps<Node<{ trigger: Trigger }>> & {
Icon?: IconType;
@@ -48,17 +43,15 @@ export function TriggerComponent({
</div>
<div className="w-full h-px bg-primary/10" />
<div className="flex flex-col gap-2 px-3 py-2">
<Suspense fallback={<div>Loading...</div>}>
<JsonSchemaForm
className="legacy"
schema={Type.Union(triggerSchemas)}
onChange={console.log}
formData={trigger}
{...props}
/*uiSchema={uiSchema}*/
/*fields={{ template: TemplateField }}*/
/>
</Suspense>
<JsonSchemaForm
className="legacy"
schema={Type.Union(triggerSchemas)}
onChange={console.log}
formData={trigger}
{...props}
/*uiSchema={uiSchema}*/
/*fields={{ template: TemplateField }}*/
/>
</div>
</div>
<Handle

View File

@@ -1,12 +1,5 @@
import type { Task } from "flows";
import { Suspense, lazy } from "react";
import { TemplateField } from "./TemplateField";
const JsonSchemaForm = lazy(() =>
import("ui/components/form/json-schema/JsonSchemaForm").then((m) => ({
default: m.JsonSchemaForm
}))
);
import { JsonSchemaForm } from "ui/components/form/json-schema";
export type TaskFormProps = {
task: Task;
@@ -26,16 +19,14 @@ export function TaskForm({ task, onChange, ...props }: TaskFormProps) {
//console.log("uiSchema", uiSchema);
return (
<Suspense fallback={<div>Loading...</div>}>
<JsonSchemaForm
className="legacy"
schema={schema}
onChange={onChange}
formData={params}
{...props}
/*uiSchema={uiSchema}*/
/*fields={{ template: TemplateField }}*/
/>
</Suspense>
<JsonSchemaForm
className="legacy"
schema={schema}
onChange={onChange}
formData={params}
{...props}
/*uiSchema={uiSchema}*/
/*fields={{ template: TemplateField }}*/
/>
);
}

View File

@@ -5,10 +5,7 @@ import { useBkndAuth } from "ui/client/schema/auth/use-bknd-auth";
import { useBkndData } from "ui/client/schema/data/use-bknd-data";
import { Button } from "ui/components/buttons/Button";
import { Alert } from "ui/components/display/Alert";
import {
JsonSchemaForm,
type JsonSchemaFormRef
} from "ui/components/form/json-schema/JsonSchemaForm";
import { JsonSchemaForm, type JsonSchemaFormRef } from "ui/components/form/json-schema";
import * as AppShell from "ui/layouts/AppShell/AppShell";
import { useNavigate } from "ui/lib/routes";
import { extractSchema } from "../settings/utils/schema";

View File

@@ -1,9 +1,7 @@
import { cloneDeep, omit } from "lodash-es";
import { useBknd } from "ui/client/bknd";
import { Button } from "ui/components/buttons/Button";
import { JsonSchemaForm } from "ui/components/form/json-schema/JsonSchemaForm";
import * as AppShell from "../../layouts/AppShell/AppShell";
import { extractSchema } from "../settings/utils/schema";
export function AuthStrategiesList() {
useBknd({ withSecrets: true });

View File

@@ -13,10 +13,7 @@ import { useBkndData } from "ui/client/schema/data/use-bknd-data";
import { Button } from "ui/components/buttons/Button";
import { IconButton } from "ui/components/buttons/IconButton";
import { Empty } from "ui/components/display/Empty";
import {
JsonSchemaForm,
type JsonSchemaFormRef
} from "ui/components/form/json-schema/JsonSchemaForm";
import { JsonSchemaForm, type JsonSchemaFormRef } from "ui/components/form/json-schema";
import { Dropdown } from "ui/components/overlay/Dropdown";
import * as AppShell from "ui/layouts/AppShell/AppShell";
import { Breadcrumbs2 } from "ui/layouts/AppShell/Breadcrumbs2";

View File

@@ -22,7 +22,7 @@ import { Button } from "ui/components/buttons/Button";
import { IconButton } from "ui/components/buttons/IconButton";
import { JsonViewer } from "ui/components/code/JsonViewer";
import { MantineSwitch } from "ui/components/form/hook-form-mantine/MantineSwitch";
import { JsonSchemaForm } from "ui/components/form/json-schema/JsonSchemaForm";
import { JsonSchemaForm } from "ui/components/form/json-schema";
import { type SortableItemProps, SortableList } from "ui/components/list/SortableList";
import { Popover } from "ui/components/overlay/Popover";
import { fieldSpecs } from "ui/modules/data/components/fields-specs";

View File

@@ -1,14 +1,19 @@
import { Suspense, lazy } from "react";
import { useBknd } from "ui/client/bknd";
import { Route, Router, Switch } from "wouter";
import AuthRoutes from "./auth";
import { AuthLogin } from "./auth/auth.login";
import DataRoutes from "./data";
import FlowRoutes from "./flows";
import MediaRoutes from "./media";
import { Root, RootEmpty } from "./root";
import SettingsRoutes from "./settings";
const DataRoutes = lazy(() => import("./data"));
/*const DataRoutes = lazy(() => import("./data"));
const AuthRoutes = lazy(() => import("./auth"));
const MediaRoutes = lazy(() => import("./media"));
const FlowRoutes = lazy(() => import("./flows"));
const SettingsRoutes = lazy(() => import("./settings"));
const SettingsRoutes = lazy(() => import("./settings"));*/
// @ts-ignore
const TestRoutes = lazy(() => import("./test"));

View File

@@ -8,10 +8,7 @@ import { Button } from "ui/components/buttons/Button";
import { IconButton } from "ui/components/buttons/IconButton";
import { Alert } from "ui/components/display/Alert";
import { Empty } from "ui/components/display/Empty";
import {
JsonSchemaForm,
type JsonSchemaFormRef
} from "ui/components/form/json-schema/JsonSchemaForm";
import { JsonSchemaForm, type JsonSchemaFormRef } from "ui/components/form/json-schema";
import { Dropdown } from "ui/components/overlay/Dropdown";
import { DataTable } from "ui/components/table/DataTable";
import { useEvent } from "ui/hooks/use-event";

View File

@@ -3,16 +3,13 @@ import type { TObject } from "core/utils";
import { omit } from "lodash-es";
import { useRef, useState } from "react";
import { TbCirclePlus, TbVariable } from "react-icons/tb";
import { useBknd } from "ui/client/BkndProvider";
import { Button } from "ui/components/buttons/Button";
import * as Formy from "ui/components/form/Formy";
import { JsonSchemaForm, type JsonSchemaFormRef } from "ui/components/form/json-schema";
import { Dropdown } from "ui/components/overlay/Dropdown";
import { Modal } from "ui/components/overlay/Modal";
import { useLocation } from "wouter";
import { useBknd } from "../../../client/BkndProvider";
import { Button } from "../../../components/buttons/Button";
import * as Formy from "../../../components/form/Formy";
import {
JsonSchemaForm,
type JsonSchemaFormRef
} from "../../../components/form/json-schema/JsonSchemaForm";
import { Dropdown } from "../../../components/overlay/Dropdown";
import { Modal } from "../../../components/overlay/Modal";
export type SettingsNewModalProps = {
schema: TObject;

View File

@@ -2,7 +2,7 @@ import { parse } from "core/utils";
import { AppFlows } from "flows/AppFlows";
import { useState } from "react";
import { JsonViewer } from "../../../components/code/JsonViewer";
import { JsonSchemaForm } from "../../../components/form/json-schema/JsonSchemaForm";
import { JsonSchemaForm } from "../../../components/form/json-schema";
import { Scrollable } from "../../../layouts/AppShell/AppShell";
export default function FlowCreateSchemaTest() {

View File

@@ -2,12 +2,9 @@ import Form from "@rjsf/core";
import type { RJSFSchema, UiSchema } from "@rjsf/utils";
import { useRef } from "react";
import { TbPlus, TbTrash } from "react-icons/tb";
import { Button } from "../../../../components/buttons/Button";
import { Button } from "ui/components/buttons/Button";
import { JsonSchemaForm, type JsonSchemaFormRef } from "ui/components/form/json-schema";
import * as Formy from "../../../../components/form/Formy";
import {
JsonSchemaForm,
type JsonSchemaFormRef
} from "../../../../components/form/json-schema/JsonSchemaForm";
import * as AppShell from "../../../../layouts/AppShell/AppShell";
class CfJsonSchemaValidator {}

View File

@@ -1,7 +1,7 @@
import type { Schema } from "@cfworker/json-schema";
import { useState } from "react";
import { JsonSchemaForm } from "../../../components/form/json-schema/JsonSchemaForm";
import { Scrollable } from "../../../layouts/AppShell/AppShell";
import { JsonSchemaForm } from "ui/components/form/json-schema";
import { Scrollable } from "ui/layouts/AppShell/AppShell";
const schema: Schema = {
definitions: {
@@ -9,52 +9,52 @@ const schema: Schema = {
anyOf: [
{
title: "String",
type: "string",
type: "string"
},
{
title: "Number",
type: "number",
type: "number"
},
{
title: "Boolean",
type: "boolean",
},
],
type: "boolean"
}
]
},
numeric: {
anyOf: [
{
title: "Number",
type: "number",
type: "number"
},
{
title: "Datetime",
type: "string",
format: "date-time",
format: "date-time"
},
{
title: "Date",
type: "string",
format: "date",
format: "date"
},
{
title: "Time",
type: "string",
format: "time",
},
],
format: "time"
}
]
},
boolean: {
title: "Boolean",
type: "boolean",
},
type: "boolean"
}
},
type: "object",
properties: {
operand: {
enum: ["$and", "$or"],
default: "$and",
type: "string",
type: "string"
},
conditions: {
type: "array",
@@ -64,10 +64,10 @@ const schema: Schema = {
operand: {
enum: ["$and", "$or"],
default: "$and",
type: "string",
type: "string"
},
key: {
type: "string",
type: "string"
},
operator: {
type: "array",
@@ -78,30 +78,30 @@ const schema: Schema = {
type: "object",
properties: {
$eq: {
$ref: "#/definitions/primitive",
},
$ref: "#/definitions/primitive"
}
},
required: ["$eq"],
required: ["$eq"]
},
{
title: "Lower than",
type: "object",
properties: {
$lt: {
$ref: "#/definitions/numeric",
},
$ref: "#/definitions/numeric"
}
},
required: ["$lt"],
required: ["$lt"]
},
{
title: "Greather than",
type: "object",
properties: {
$gt: {
$ref: "#/definitions/numeric",
},
$ref: "#/definitions/numeric"
}
},
required: ["$gt"],
required: ["$gt"]
},
{
title: "Between",
@@ -110,13 +110,13 @@ const schema: Schema = {
$between: {
type: "array",
items: {
$ref: "#/definitions/numeric",
$ref: "#/definitions/numeric"
},
minItems: 2,
maxItems: 2,
},
maxItems: 2
}
},
required: ["$between"],
required: ["$between"]
},
{
title: "In",
@@ -125,23 +125,23 @@ const schema: Schema = {
$in: {
type: "array",
items: {
$ref: "#/definitions/primitive",
$ref: "#/definitions/primitive"
},
minItems: 1,
},
},
},
],
minItems: 1
}
}
}
]
},
minItems: 1,
},
minItems: 1
}
},
required: ["key", "operator"],
required: ["key", "operator"]
},
minItems: 1,
},
minItems: 1
}
},
required: ["operand", "conditions"],
required: ["operand", "conditions"]
};
export default function QueryJsonFormTest() {

View File

@@ -1,8 +1,8 @@
import { useEffect, useState } from "react";
import { twMerge } from "tailwind-merge";
import { useBknd } from "../../../client/BkndProvider";
import { JsonSchemaForm } from "../../../components/form/json-schema/JsonSchemaForm";
import { Scrollable } from "../../../layouts/AppShell/AppShell";
import { useBknd } from "ui/client/BkndProvider";
import { JsonSchemaForm } from "ui/components/form/json-schema";
import { Scrollable } from "ui/layouts/AppShell/AppShell";
function useSchema() {
const [schema, setSchema] = useState<any>();

View File

@@ -1,7 +1,20 @@
import { useEffect, useState } from "react";
import { useApiQuery } from "ui/client";
import { useApi, useApiQuery } from "ui/client";
import { Scrollable } from "ui/layouts/AppShell/AppShell";
function Bla() {
const api = useApi();
useEffect(() => {
(async () => {
const one = await api.data.readOne("users", 1);
const many = await api.data.readMany("users");
})();
}, []);
return null;
}
export default function SWRAndAPI() {
const [text, setText] = useState("");
const { data, ...r } = useApiQuery((api) => api.data.readOne("comments", 1), {
@@ -16,7 +29,7 @@ export default function SWRAndAPI() {
return (
<Scrollable>
<pre>{JSON.stringify(r.promise.keyArray({ search: false }))}</pre>
<pre>{JSON.stringify(r.key)}</pre>
{r.error && <div>failed to load</div>}
{r.isLoading && <div>loading...</div>}
{data && <pre>{JSON.stringify(data, null, 2)}</pre>}

View File

@@ -13,7 +13,7 @@ export default function SwrAndDataApi() {
function QueryDataApi() {
const [text, setText] = useState("");
const { data, update, ...r } = useEntityQuery("comments", 1, {});
const { data, update, ...r } = useEntityQuery("comments", 2);
const comment = data ? data : null;
useEffect(() => {
@@ -45,10 +45,10 @@ function QueryDataApi() {
function DirectDataApi() {
const [data, setData] = useState<any>();
const { create, read, update, _delete } = useEntity("comments", 1);
const { create, read, update, _delete } = useEntity("users");
useEffect(() => {
read().then(setData);
read().then((data) => setData(data));
}, []);
return <pre>{JSON.stringify(data, null, 2)}</pre>;