mirror of
https://github.com/shishantbiswas/bknd.git
synced 2026-03-16 04:27:21 +00:00
optimized module manager seeding, added type support for new api hooks and reduced amount of dist chunks
This commit is contained in:
@@ -3,6 +3,7 @@ import {
|
|||||||
BooleanField,
|
BooleanField,
|
||||||
DateField,
|
DateField,
|
||||||
Entity,
|
Entity,
|
||||||
|
EntityManager,
|
||||||
EnumField,
|
EnumField,
|
||||||
JsonField,
|
JsonField,
|
||||||
ManyToManyRelation,
|
ManyToManyRelation,
|
||||||
@@ -46,12 +47,17 @@ describe("prototype", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("...2", async () => {
|
test("...2", async () => {
|
||||||
const user = entity("users", {
|
const users = entity("users", {
|
||||||
name: text().required(),
|
name: text(),
|
||||||
bio: text(),
|
bio: text(),
|
||||||
age: number(),
|
age: number(),
|
||||||
some: number().required()
|
some: number()
|
||||||
});
|
});
|
||||||
|
type db = {
|
||||||
|
users: Schema<typeof users>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const obj: Schema<typeof users> = {} as any;
|
||||||
|
|
||||||
//console.log("user", user.toJSON());
|
//console.log("user", user.toJSON());
|
||||||
});
|
});
|
||||||
|
|||||||
50
app/build.ts
50
app/build.ts
@@ -9,16 +9,44 @@ const watch = args.includes("--watch");
|
|||||||
const minify = args.includes("--minify");
|
const minify = args.includes("--minify");
|
||||||
const types = args.includes("--types");
|
const types = args.includes("--types");
|
||||||
const sourcemap = args.includes("--sourcemap");
|
const sourcemap = args.includes("--sourcemap");
|
||||||
|
const clean = args.includes("--clean");
|
||||||
|
|
||||||
|
if (clean) {
|
||||||
|
console.log("Cleaning dist");
|
||||||
|
await $`rm -rf dist`;
|
||||||
|
}
|
||||||
|
|
||||||
|
let types_running = false;
|
||||||
|
function buildTypes() {
|
||||||
|
if (types_running) return;
|
||||||
|
types_running = true;
|
||||||
|
|
||||||
await $`rm -rf dist`;
|
|
||||||
if (types) {
|
|
||||||
Bun.spawn(["bun", "build:types"], {
|
Bun.spawn(["bun", "build:types"], {
|
||||||
onExit: () => {
|
onExit: () => {
|
||||||
console.log("Types built");
|
console.log("Types built");
|
||||||
|
Bun.spawn(["bun", "tsc-alias"], {
|
||||||
|
onExit: () => {
|
||||||
|
console.log("Types aliased");
|
||||||
|
types_running = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let watcher_timeout: any;
|
||||||
|
function delayTypes() {
|
||||||
|
if (!watch) return;
|
||||||
|
if (watcher_timeout) {
|
||||||
|
clearTimeout(watcher_timeout);
|
||||||
|
}
|
||||||
|
watcher_timeout = setTimeout(buildTypes, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (types && !watch) {
|
||||||
|
buildTypes();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build static assets
|
* Build static assets
|
||||||
* Using esbuild because tsup doesn't include "react"
|
* Using esbuild because tsup doesn't include "react"
|
||||||
@@ -46,7 +74,8 @@ const result = await esbuild.build({
|
|||||||
__isDev: "0",
|
__isDev: "0",
|
||||||
"process.env.NODE_ENV": '"production"'
|
"process.env.NODE_ENV": '"production"'
|
||||||
},
|
},
|
||||||
chunkNames: "chunks/[name]-[hash]"
|
chunkNames: "chunks/[name]-[hash]",
|
||||||
|
logLevel: "error"
|
||||||
});
|
});
|
||||||
|
|
||||||
// Write manifest
|
// Write manifest
|
||||||
@@ -96,6 +125,9 @@ await tsup.build({
|
|||||||
treeshake: true,
|
treeshake: true,
|
||||||
loader: {
|
loader: {
|
||||||
".svg": "dataurl"
|
".svg": "dataurl"
|
||||||
|
},
|
||||||
|
onSuccess: async () => {
|
||||||
|
delayTypes();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -117,11 +149,12 @@ await tsup.build({
|
|||||||
loader: {
|
loader: {
|
||||||
".svg": "dataurl"
|
".svg": "dataurl"
|
||||||
},
|
},
|
||||||
onSuccess: async () => {
|
|
||||||
console.log("--- ui built");
|
|
||||||
},
|
|
||||||
esbuildOptions: (options) => {
|
esbuildOptions: (options) => {
|
||||||
|
options.logLevel = "silent";
|
||||||
options.chunkNames = "chunks/[name]-[hash]";
|
options.chunkNames = "chunks/[name]-[hash]";
|
||||||
|
},
|
||||||
|
onSuccess: async () => {
|
||||||
|
delayTypes();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -148,7 +181,10 @@ function baseConfig(adapter: string): tsup.Options {
|
|||||||
],
|
],
|
||||||
metafile: true,
|
metafile: true,
|
||||||
splitting: false,
|
splitting: false,
|
||||||
treeshake: true
|
treeshake: true,
|
||||||
|
onSuccess: async () => {
|
||||||
|
delayTypes();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,14 +5,14 @@
|
|||||||
"bin": "./dist/cli/index.js",
|
"bin": "./dist/cli/index.js",
|
||||||
"version": "0.3.4-alpha1",
|
"version": "0.3.4-alpha1",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build:all": "bun run build && bun run build:cli",
|
"build:all": "NODE_ENV=production bun run build.ts --minify --types --clean && bun run build:cli",
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"test": "ALL_TESTS=1 bun test --bail",
|
"test": "ALL_TESTS=1 bun test --bail",
|
||||||
"build": "NODE_ENV=production bun run build.ts --minify --types",
|
"build": "NODE_ENV=production bun run build.ts --minify --types",
|
||||||
"watch": "bun run build.ts --types --watch",
|
"watch": "bun run build.ts --types --watch",
|
||||||
"types": "bun tsc --noEmit",
|
"types": "bun tsc --noEmit",
|
||||||
"clean:types": "find ./dist -name '*.d.ts' -delete && rm -f ./dist/tsconfig.tsbuildinfo",
|
"clean:types": "find ./dist -name '*.d.ts' -delete && rm -f ./dist/tsconfig.tsbuildinfo",
|
||||||
"build:types": "tsc --emitDeclarationOnly",
|
"build:types": "tsc --emitDeclarationOnly && tsc-alias",
|
||||||
"build:css": "bun tailwindcss -i src/ui/main.css -o ./dist/static/styles.css",
|
"build:css": "bun tailwindcss -i src/ui/main.css -o ./dist/static/styles.css",
|
||||||
"watch:css": "bun tailwindcss --watch -i src/ui/main.css -o ./dist/styles.css",
|
"watch:css": "bun tailwindcss --watch -i src/ui/main.css -o ./dist/styles.css",
|
||||||
"updater": "bun x npm-check-updates -ui",
|
"updater": "bun x npm-check-updates -ui",
|
||||||
@@ -75,6 +75,7 @@
|
|||||||
"tailwind-merge": "^2.5.4",
|
"tailwind-merge": "^2.5.4",
|
||||||
"tailwindcss": "^3.4.14",
|
"tailwindcss": "^3.4.14",
|
||||||
"tailwindcss-animate": "^1.0.7",
|
"tailwindcss-animate": "^1.0.7",
|
||||||
|
"tsc-alias": "^1.8.10",
|
||||||
"tsup": "^8.3.5",
|
"tsup": "^8.3.5",
|
||||||
"vite": "^5.4.10",
|
"vite": "^5.4.10",
|
||||||
"vite-plugin-static-copy": "^2.0.0",
|
"vite-plugin-static-copy": "^2.0.0",
|
||||||
@@ -90,75 +91,75 @@
|
|||||||
},
|
},
|
||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
"module": "./dist/index.js",
|
"module": "./dist/index.js",
|
||||||
"types": "./dist/index.d.ts",
|
"types": "./dist/types/index.d.ts",
|
||||||
"exports": {
|
"exports": {
|
||||||
".": {
|
".": {
|
||||||
"types": "./dist/index.d.ts",
|
"types": "./dist/types/index.d.ts",
|
||||||
"import": "./dist/index.js",
|
"import": "./dist/index.js",
|
||||||
"require": "./dist/index.cjs"
|
"require": "./dist/index.cjs"
|
||||||
},
|
},
|
||||||
"./ui": {
|
"./ui": {
|
||||||
"types": "./dist/ui/index.d.ts",
|
"types": "./dist/types/ui/index.d.ts",
|
||||||
"import": "./dist/ui/index.js",
|
"import": "./dist/ui/index.js",
|
||||||
"require": "./dist/ui/index.cjs"
|
"require": "./dist/ui/index.cjs"
|
||||||
},
|
},
|
||||||
"./client": {
|
"./client": {
|
||||||
"types": "./dist/ui/client/index.d.ts",
|
"types": "./dist/types/ui/client/index.d.ts",
|
||||||
"import": "./dist/ui/client/index.js",
|
"import": "./dist/ui/client/index.js",
|
||||||
"require": "./dist/ui/client/index.cjs"
|
"require": "./dist/ui/client/index.cjs"
|
||||||
},
|
},
|
||||||
"./data": {
|
"./data": {
|
||||||
"types": "./dist/data/index.d.ts",
|
"types": "./dist/types/data/index.d.ts",
|
||||||
"import": "./dist/data/index.js",
|
"import": "./dist/data/index.js",
|
||||||
"require": "./dist/data/index.cjs"
|
"require": "./dist/data/index.cjs"
|
||||||
},
|
},
|
||||||
"./core": {
|
"./core": {
|
||||||
"types": "./dist/core/index.d.ts",
|
"types": "./dist/types/core/index.d.ts",
|
||||||
"import": "./dist/core/index.js",
|
"import": "./dist/core/index.js",
|
||||||
"require": "./dist/core/index.cjs"
|
"require": "./dist/core/index.cjs"
|
||||||
},
|
},
|
||||||
"./utils": {
|
"./utils": {
|
||||||
"types": "./dist/core/utils/index.d.ts",
|
"types": "./dist/types/core/utils/index.d.ts",
|
||||||
"import": "./dist/core/utils/index.js",
|
"import": "./dist/core/utils/index.js",
|
||||||
"require": "./dist/core/utils/index.cjs"
|
"require": "./dist/core/utils/index.cjs"
|
||||||
},
|
},
|
||||||
"./cli": {
|
"./cli": {
|
||||||
"types": "./dist/cli/index.d.ts",
|
"types": "./dist/types/cli/index.d.ts",
|
||||||
"import": "./dist/cli/index.js",
|
"import": "./dist/cli/index.js",
|
||||||
"require": "./dist/cli/index.cjs"
|
"require": "./dist/cli/index.cjs"
|
||||||
},
|
},
|
||||||
"./adapter/cloudflare": {
|
"./adapter/cloudflare": {
|
||||||
"types": "./dist/adapter/cloudflare/index.d.ts",
|
"types": "./dist/types/adapter/cloudflare/index.d.ts",
|
||||||
"import": "./dist/adapter/cloudflare/index.js",
|
"import": "./dist/adapter/cloudflare/index.js",
|
||||||
"require": "./dist/adapter/cloudflare/index.cjs"
|
"require": "./dist/adapter/cloudflare/index.cjs"
|
||||||
},
|
},
|
||||||
"./adapter/vite": {
|
"./adapter/vite": {
|
||||||
"types": "./dist/adapter/vite/index.d.ts",
|
"types": "./dist/types/adapter/vite/index.d.ts",
|
||||||
"import": "./dist/adapter/vite/index.js",
|
"import": "./dist/adapter/vite/index.js",
|
||||||
"require": "./dist/adapter/vite/index.cjs"
|
"require": "./dist/adapter/vite/index.cjs"
|
||||||
},
|
},
|
||||||
"./adapter/nextjs": {
|
"./adapter/nextjs": {
|
||||||
"types": "./dist/adapter/nextjs/index.d.ts",
|
"types": "./dist/types/adapter/nextjs/index.d.ts",
|
||||||
"import": "./dist/adapter/nextjs/index.js",
|
"import": "./dist/adapter/nextjs/index.js",
|
||||||
"require": "./dist/adapter/nextjs/index.cjs"
|
"require": "./dist/adapter/nextjs/index.cjs"
|
||||||
},
|
},
|
||||||
"./adapter/remix": {
|
"./adapter/remix": {
|
||||||
"types": "./dist/adapter/remix/index.d.ts",
|
"types": "./dist/types/adapter/remix/index.d.ts",
|
||||||
"import": "./dist/adapter/remix/index.js",
|
"import": "./dist/adapter/remix/index.js",
|
||||||
"require": "./dist/adapter/remix/index.cjs"
|
"require": "./dist/adapter/remix/index.cjs"
|
||||||
},
|
},
|
||||||
"./adapter/bun": {
|
"./adapter/bun": {
|
||||||
"types": "./dist/adapter/bun/index.d.ts",
|
"types": "./dist/types/adapter/bun/index.d.ts",
|
||||||
"import": "./dist/adapter/bun/index.js",
|
"import": "./dist/adapter/bun/index.js",
|
||||||
"require": "./dist/adapter/bun/index.cjs"
|
"require": "./dist/adapter/bun/index.cjs"
|
||||||
},
|
},
|
||||||
"./adapter/node": {
|
"./adapter/node": {
|
||||||
"types": "./dist/adapter/node/index.d.ts",
|
"types": "./dist/types/adapter/node/index.d.ts",
|
||||||
"import": "./dist/adapter/node/index.js",
|
"import": "./dist/adapter/node/index.js",
|
||||||
"require": "./dist/adapter/node/index.cjs"
|
"require": "./dist/adapter/node/index.cjs"
|
||||||
},
|
},
|
||||||
"./adapter/astro": {
|
"./adapter/astro": {
|
||||||
"types": "./dist/adapter/astro/index.d.ts",
|
"types": "./dist/types/adapter/astro/index.d.ts",
|
||||||
"import": "./dist/adapter/astro/index.js",
|
"import": "./dist/adapter/astro/index.js",
|
||||||
"require": "./dist/adapter/astro/index.cjs"
|
"require": "./dist/adapter/astro/index.cjs"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ export class Api {
|
|||||||
private token_transport: "header" | "cookie" | "none" = "header";
|
private token_transport: "header" | "cookie" | "none" = "header";
|
||||||
|
|
||||||
public system!: SystemApi;
|
public system!: SystemApi;
|
||||||
public data!: DataApi;
|
public data!: DataApi<DB>;
|
||||||
public auth!: AuthApi;
|
public auth!: AuthApi;
|
||||||
public media!: MediaApi;
|
public media!: MediaApi;
|
||||||
|
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ export type AppConfig = InitialModuleConfigs;
|
|||||||
export class App<DB = any> {
|
export class App<DB = any> {
|
||||||
modules: ModuleManager;
|
modules: ModuleManager;
|
||||||
static readonly Events = AppEvents;
|
static readonly Events = AppEvents;
|
||||||
|
adminController?: AdminController;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private connection: Connection,
|
private connection: Connection,
|
||||||
@@ -94,8 +95,12 @@ export class App<DB = any> {
|
|||||||
return this.modules.get(module).schema();
|
return this.modules.get(module).schema();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get server() {
|
||||||
|
return this.modules.server;
|
||||||
|
}
|
||||||
|
|
||||||
get fetch(): any {
|
get fetch(): any {
|
||||||
return this.modules.server.fetch;
|
return this.server.fetch;
|
||||||
}
|
}
|
||||||
|
|
||||||
get module() {
|
get module() {
|
||||||
@@ -119,7 +124,8 @@ export class App<DB = any> {
|
|||||||
|
|
||||||
registerAdminController(config?: AdminControllerOptions) {
|
registerAdminController(config?: AdminControllerOptions) {
|
||||||
// register admin
|
// register admin
|
||||||
this.modules.server.route("/", new AdminController(this, config).getController());
|
this.adminController = new AdminController(this, config);
|
||||||
|
this.modules.server.route("/", this.adminController.getController());
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -197,7 +197,7 @@ export class AppAuth extends Module<typeof authConfigSchema> {
|
|||||||
throw new Exception("User already exists");
|
throw new Exception("User already exists");
|
||||||
}
|
}
|
||||||
|
|
||||||
const payload = {
|
const payload: any = {
|
||||||
...profile,
|
...profile,
|
||||||
strategy: strategy.getName(),
|
strategy: strategy.getName(),
|
||||||
strategy_value: identifier
|
strategy_value: identifier
|
||||||
|
|||||||
@@ -9,10 +9,25 @@ export async function withDisabledConsole<R>(
|
|||||||
fn: () => Promise<R>,
|
fn: () => Promise<R>,
|
||||||
severities: ConsoleSeverity[] = ["log"]
|
severities: ConsoleSeverity[] = ["log"]
|
||||||
): Promise<R> {
|
): Promise<R> {
|
||||||
const enable = disableConsoleLog(severities);
|
const _oldConsoles = {
|
||||||
const result = await fn();
|
log: console.log,
|
||||||
enable();
|
warn: console.warn,
|
||||||
return result;
|
error: console.error
|
||||||
|
};
|
||||||
|
disableConsoleLog(severities);
|
||||||
|
const enable = () => {
|
||||||
|
Object.entries(_oldConsoles).forEach(([severity, fn]) => {
|
||||||
|
console[severity as ConsoleSeverity] = fn;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
const result = await fn();
|
||||||
|
enable();
|
||||||
|
return result;
|
||||||
|
} catch (e) {
|
||||||
|
enable();
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function disableConsoleLog(severities: ConsoleSeverity[] = ["log"]) {
|
export function disableConsoleLog(severities: ConsoleSeverity[] = ["log"]) {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ export type DataApiOptions = BaseModuleApiOptions & {
|
|||||||
defaultQuery?: Partial<RepoQuery>;
|
defaultQuery?: Partial<RepoQuery>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export class DataApi extends ModuleApi<DataApiOptions> {
|
export class DataApi<DB> extends ModuleApi<DataApiOptions> {
|
||||||
protected override getDefaultOptions(): Partial<DataApiOptions> {
|
protected override getDefaultOptions(): Partial<DataApiOptions> {
|
||||||
return {
|
return {
|
||||||
basepath: "/api/data",
|
basepath: "/api/data",
|
||||||
@@ -15,48 +15,60 @@ export class DataApi extends ModuleApi<DataApiOptions> {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
readOne(
|
readOne<E extends keyof DB | string, Data = E extends keyof DB ? DB[E] : EntityData>(
|
||||||
entity: string,
|
entity: E,
|
||||||
id: PrimaryFieldType,
|
id: PrimaryFieldType,
|
||||||
query: Partial<Omit<RepoQuery, "where" | "limit" | "offset">> = {}
|
query: Partial<Omit<RepoQuery, "where" | "limit" | "offset">> = {}
|
||||||
) {
|
) {
|
||||||
return this.get<RepositoryResponse<EntityData>>([entity, id], query);
|
return this.get<Pick<RepositoryResponse<Data>, "meta" | "data">>([entity as any, id], query);
|
||||||
}
|
}
|
||||||
|
|
||||||
readMany(entity: string, query: Partial<RepoQuery> = {}) {
|
readMany<E extends keyof DB | string, Data = E extends keyof DB ? DB[E] : EntityData>(
|
||||||
return this.get<Pick<RepositoryResponse, "meta" | "data">>(
|
entity: E,
|
||||||
[entity],
|
|
||||||
query ?? this.options.defaultQuery
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
readManyByReference(
|
|
||||||
entity: string,
|
|
||||||
id: PrimaryFieldType,
|
|
||||||
reference: string,
|
|
||||||
query: Partial<RepoQuery> = {}
|
query: Partial<RepoQuery> = {}
|
||||||
) {
|
) {
|
||||||
return this.get<Pick<RepositoryResponse, "meta" | "data">>(
|
return this.get<Pick<RepositoryResponse<Data[]>, "meta" | "data">>(
|
||||||
[entity, id, reference],
|
[entity as any],
|
||||||
query ?? this.options.defaultQuery
|
query ?? this.options.defaultQuery
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
createOne(entity: string, input: EntityData) {
|
readManyByReference<
|
||||||
return this.post<RepositoryResponse<EntityData>>([entity], input);
|
E extends keyof DB | string,
|
||||||
|
R extends keyof DB | string,
|
||||||
|
Data = R extends keyof DB ? DB[R] : EntityData
|
||||||
|
>(entity: E, id: PrimaryFieldType, reference: R, query: Partial<RepoQuery> = {}) {
|
||||||
|
return this.get<Pick<RepositoryResponse<Data[]>, "meta" | "data">>(
|
||||||
|
[entity as any, id, reference],
|
||||||
|
query ?? this.options.defaultQuery
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateOne(entity: string, id: PrimaryFieldType, input: EntityData) {
|
createOne<E extends keyof DB | string, Data = E extends keyof DB ? DB[E] : EntityData>(
|
||||||
return this.patch<RepositoryResponse<EntityData>>([entity, id], input);
|
entity: E,
|
||||||
|
input: Data
|
||||||
|
) {
|
||||||
|
return this.post<RepositoryResponse<Data>>([entity as any], input);
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteOne(entity: string, id: PrimaryFieldType) {
|
updateOne<E extends keyof DB | string, Data = E extends keyof DB ? DB[E] : EntityData>(
|
||||||
return this.delete<RepositoryResponse<EntityData>>([entity, id]);
|
entity: E,
|
||||||
|
id: PrimaryFieldType,
|
||||||
|
input: Partial<Data>
|
||||||
|
) {
|
||||||
|
return this.patch<RepositoryResponse<Data>>([entity as any, id], input);
|
||||||
}
|
}
|
||||||
|
|
||||||
count(entity: string, where: RepoQuery["where"] = {}) {
|
deleteOne<E extends keyof DB | string, Data = E extends keyof DB ? DB[E] : EntityData>(
|
||||||
return this.post<RepositoryResponse<{ entity: string; count: number }>>(
|
entity: E,
|
||||||
[entity, "fn", "count"],
|
id: PrimaryFieldType
|
||||||
|
) {
|
||||||
|
return this.delete<RepositoryResponse<Data>>([entity as any, id]);
|
||||||
|
}
|
||||||
|
|
||||||
|
count<E extends keyof DB | string>(entity: E, where: RepoQuery["where"] = {}) {
|
||||||
|
return this.post<RepositoryResponse<{ entity: E; count: number }>>(
|
||||||
|
[entity as any, "fn", "count"],
|
||||||
where
|
where
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -165,13 +165,12 @@ export class DataController implements ClassController {
|
|||||||
// read entity schema
|
// read entity schema
|
||||||
.get("/schema.json", async (c) => {
|
.get("/schema.json", async (c) => {
|
||||||
this.guard.throwUnlessGranted(DataPermissions.entityRead);
|
this.guard.throwUnlessGranted(DataPermissions.entityRead);
|
||||||
const url = new URL(c.req.url);
|
const $id = `${this.config.basepath}/schema.json`;
|
||||||
const $id = `${url.origin}${this.config.basepath}/schema.json`;
|
|
||||||
const schemas = Object.fromEntries(
|
const schemas = Object.fromEntries(
|
||||||
this.em.entities.map((e) => [
|
this.em.entities.map((e) => [
|
||||||
e.name,
|
e.name,
|
||||||
{
|
{
|
||||||
$ref: `schemas/${e.name}`
|
$ref: `${this.config.basepath}/schemas/${e.name}`
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
);
|
);
|
||||||
@@ -198,7 +197,7 @@ export class DataController implements ClassController {
|
|||||||
const schema = _entity.toSchema();
|
const schema = _entity.toSchema();
|
||||||
const url = new URL(c.req.url);
|
const url = new URL(c.req.url);
|
||||||
const base = `${url.origin}${this.config.basepath}`;
|
const base = `${url.origin}${this.config.basepath}`;
|
||||||
const $id = `${base}/schemas/${entity}`;
|
const $id = `${this.config.basepath}/schemas/${entity}`;
|
||||||
return c.json({
|
return c.json({
|
||||||
$schema: `${base}/schema.json`,
|
$schema: `${base}/schema.json`,
|
||||||
$id,
|
$id,
|
||||||
|
|||||||
@@ -14,6 +14,14 @@ import { SchemaManager } from "../schema/SchemaManager";
|
|||||||
import { Entity } from "./Entity";
|
import { Entity } from "./Entity";
|
||||||
import { type EntityData, Mutator, Repository } from "./index";
|
import { type EntityData, Mutator, Repository } from "./index";
|
||||||
|
|
||||||
|
type EntitySchema<E extends Entity | string, DB = any> = E extends Entity<infer Name>
|
||||||
|
? Name extends keyof DB
|
||||||
|
? Name
|
||||||
|
: never
|
||||||
|
: E extends keyof DB
|
||||||
|
? E
|
||||||
|
: never;
|
||||||
|
|
||||||
export class EntityManager<DB> {
|
export class EntityManager<DB> {
|
||||||
connection: Connection;
|
connection: Connection;
|
||||||
|
|
||||||
@@ -87,10 +95,16 @@ export class EntityManager<DB> {
|
|||||||
this.entities.push(entity);
|
this.entities.push(entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
entity(name: string): Entity {
|
entity(e: Entity | string): Entity {
|
||||||
const entity = this.entities.find((e) => e.name === name);
|
let entity: Entity | undefined;
|
||||||
|
if (typeof e === "string") {
|
||||||
|
entity = this.entities.find((entity) => entity.name === e);
|
||||||
|
} else {
|
||||||
|
entity = e;
|
||||||
|
}
|
||||||
|
|
||||||
if (!entity) {
|
if (!entity) {
|
||||||
throw new EntityNotDefinedException(name);
|
throw new EntityNotDefinedException(typeof e === "string" ? e : e.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
return entity;
|
return entity;
|
||||||
@@ -162,28 +176,16 @@ export class EntityManager<DB> {
|
|||||||
return this.relations.relationReferencesOf(this.entity(entity_name));
|
return this.relations.relationReferencesOf(this.entity(entity_name));
|
||||||
}
|
}
|
||||||
|
|
||||||
repository(_entity: Entity | string) {
|
repository<E extends Entity | string>(entity: E): Repository<DB, EntitySchema<E, DB>> {
|
||||||
const entity = _entity instanceof Entity ? _entity : this.entity(_entity);
|
return this.repo(entity);
|
||||||
return new Repository(this, entity, this.emgr);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
repo<E extends Entity>(
|
repo<E extends Entity | string>(entity: E): Repository<DB, EntitySchema<E, DB>> {
|
||||||
_entity: E
|
return new Repository(this, this.entity(entity), this.emgr);
|
||||||
): Repository<
|
|
||||||
DB,
|
|
||||||
E extends Entity<infer Name> ? (Name extends keyof DB ? Name : never) : never
|
|
||||||
> {
|
|
||||||
return new Repository(this, _entity, this.emgr);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_repo<TB extends keyof DB>(_entity: TB): Repository<DB, TB> {
|
mutator<E extends Entity | string>(entity: E): Mutator<DB, EntitySchema<E, DB>> {
|
||||||
const entity = this.entity(_entity as any);
|
return new Mutator(this, this.entity(entity), this.emgr);
|
||||||
return new Repository(this, entity, this.emgr);
|
|
||||||
}
|
|
||||||
|
|
||||||
mutator(_entity: Entity | string) {
|
|
||||||
const entity = _entity instanceof Entity ? _entity : this.entity(_entity);
|
|
||||||
return new Mutator(this, entity, this.emgr);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
addIndex(index: EntityIndex, force = false) {
|
addIndex(index: EntityIndex, force = false) {
|
||||||
|
|||||||
@@ -25,7 +25,9 @@ export type MutatorResponse<T = EntityData[]> = {
|
|||||||
data: T;
|
data: T;
|
||||||
};
|
};
|
||||||
|
|
||||||
export class Mutator<DB> implements EmitsEvents {
|
export class Mutator<DB = any, TB extends keyof DB = any, Data = Omit<DB[TB], "id">>
|
||||||
|
implements EmitsEvents
|
||||||
|
{
|
||||||
em: EntityManager<DB>;
|
em: EntityManager<DB>;
|
||||||
entity: Entity;
|
entity: Entity;
|
||||||
static readonly Events = MutatorEvents;
|
static readonly Events = MutatorEvents;
|
||||||
@@ -47,13 +49,13 @@ export class Mutator<DB> implements EmitsEvents {
|
|||||||
return this.em.connection.kysely;
|
return this.em.connection.kysely;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getValidatedData(data: EntityData, context: TActionContext): Promise<EntityData> {
|
async getValidatedData<Given = any>(data: Given, context: TActionContext): Promise<Given> {
|
||||||
const entity = this.entity;
|
const entity = this.entity;
|
||||||
if (!context) {
|
if (!context) {
|
||||||
throw new Error("Context must be provided for validation");
|
throw new Error("Context must be provided for validation");
|
||||||
}
|
}
|
||||||
|
|
||||||
const keys = Object.keys(data);
|
const keys = Object.keys(data as any);
|
||||||
const validatedData: EntityData = {};
|
const validatedData: EntityData = {};
|
||||||
|
|
||||||
// get relational references/keys
|
// get relational references/keys
|
||||||
@@ -95,7 +97,7 @@ export class Mutator<DB> implements EmitsEvents {
|
|||||||
throw new Error(`No data left to update "${entity.name}"`);
|
throw new Error(`No data left to update "${entity.name}"`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return validatedData;
|
return validatedData as Given;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async many(qb: MutatorQB): Promise<MutatorResponse> {
|
protected async many(qb: MutatorQB): Promise<MutatorResponse> {
|
||||||
@@ -120,7 +122,7 @@ export class Mutator<DB> implements EmitsEvents {
|
|||||||
return { ...response, data: data[0]! };
|
return { ...response, data: data[0]! };
|
||||||
}
|
}
|
||||||
|
|
||||||
async insertOne(data: EntityData): Promise<MutatorResponse<EntityData>> {
|
async insertOne(data: Data): Promise<MutatorResponse<DB[TB]>> {
|
||||||
const entity = this.entity;
|
const entity = this.entity;
|
||||||
if (entity.type === "system" && this.__unstable_disable_system_entity_creation) {
|
if (entity.type === "system" && this.__unstable_disable_system_entity_creation) {
|
||||||
throw new Error(`Creation of system entity "${entity.name}" is disabled`);
|
throw new Error(`Creation of system entity "${entity.name}" is disabled`);
|
||||||
@@ -154,10 +156,10 @@ export class Mutator<DB> implements EmitsEvents {
|
|||||||
|
|
||||||
await this.emgr.emit(new Mutator.Events.MutatorInsertAfter({ entity, data: res.data }));
|
await this.emgr.emit(new Mutator.Events.MutatorInsertAfter({ entity, data: res.data }));
|
||||||
|
|
||||||
return res;
|
return res as any;
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateOne(id: PrimaryFieldType, data: EntityData): Promise<MutatorResponse<EntityData>> {
|
async updateOne(id: PrimaryFieldType, data: Data): Promise<MutatorResponse<DB[TB]>> {
|
||||||
const entity = this.entity;
|
const entity = this.entity;
|
||||||
if (!Number.isInteger(id)) {
|
if (!Number.isInteger(id)) {
|
||||||
throw new Error("ID must be provided for update");
|
throw new Error("ID must be provided for update");
|
||||||
@@ -166,12 +168,16 @@ export class Mutator<DB> implements EmitsEvents {
|
|||||||
const validatedData = await this.getValidatedData(data, "update");
|
const validatedData = await this.getValidatedData(data, "update");
|
||||||
|
|
||||||
await this.emgr.emit(
|
await this.emgr.emit(
|
||||||
new Mutator.Events.MutatorUpdateBefore({ entity, entityId: id, data: validatedData })
|
new Mutator.Events.MutatorUpdateBefore({
|
||||||
|
entity,
|
||||||
|
entityId: id,
|
||||||
|
data: validatedData as any
|
||||||
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
const query = this.conn
|
const query = this.conn
|
||||||
.updateTable(entity.name)
|
.updateTable(entity.name)
|
||||||
.set(validatedData)
|
.set(validatedData as any)
|
||||||
.where(entity.id().name, "=", id)
|
.where(entity.id().name, "=", id)
|
||||||
.returning(entity.getSelect());
|
.returning(entity.getSelect());
|
||||||
|
|
||||||
@@ -181,10 +187,10 @@ export class Mutator<DB> implements EmitsEvents {
|
|||||||
new Mutator.Events.MutatorUpdateAfter({ entity, entityId: id, data: res.data })
|
new Mutator.Events.MutatorUpdateAfter({ entity, entityId: id, data: res.data })
|
||||||
);
|
);
|
||||||
|
|
||||||
return res;
|
return res as any;
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteOne(id: PrimaryFieldType): Promise<MutatorResponse<EntityData>> {
|
async deleteOne(id: PrimaryFieldType): Promise<MutatorResponse<DB[TB]>> {
|
||||||
const entity = this.entity;
|
const entity = this.entity;
|
||||||
if (!Number.isInteger(id)) {
|
if (!Number.isInteger(id)) {
|
||||||
throw new Error("ID must be provided for deletion");
|
throw new Error("ID must be provided for deletion");
|
||||||
@@ -203,7 +209,7 @@ export class Mutator<DB> implements EmitsEvents {
|
|||||||
new Mutator.Events.MutatorDeleteAfter({ entity, entityId: id, data: res.data })
|
new Mutator.Events.MutatorDeleteAfter({ entity, entityId: id, data: res.data })
|
||||||
);
|
);
|
||||||
|
|
||||||
return res;
|
return res as any;
|
||||||
}
|
}
|
||||||
|
|
||||||
private getValidOptions(options?: Partial<RepoQuery>): Partial<RepoQuery> {
|
private getValidOptions(options?: Partial<RepoQuery>): Partial<RepoQuery> {
|
||||||
@@ -250,47 +256,24 @@ export class Mutator<DB> implements EmitsEvents {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// @todo: decide whether entries should be deleted all at once or one by one (for events)
|
// @todo: decide whether entries should be deleted all at once or one by one (for events)
|
||||||
async deleteWhere(where?: RepoQuery["where"]): Promise<MutatorResponse<EntityData>> {
|
async deleteWhere(where?: RepoQuery["where"]): Promise<MutatorResponse<DB[TB][]>> {
|
||||||
const entity = this.entity;
|
const entity = this.entity;
|
||||||
|
|
||||||
const qb = this.appendWhere(this.conn.deleteFrom(entity.name), where).returning(
|
const qb = this.appendWhere(this.conn.deleteFrom(entity.name), where).returning(
|
||||||
entity.getSelect()
|
entity.getSelect()
|
||||||
);
|
);
|
||||||
|
|
||||||
//await this.emgr.emit(new Mutator.Events.MutatorDeleteBefore({ entity, entityId: id }));
|
return (await this.many(qb)) as any;
|
||||||
|
|
||||||
const res = await this.many(qb);
|
|
||||||
|
|
||||||
/*await this.emgr.emit(
|
|
||||||
new Mutator.Events.MutatorDeleteAfter({ entity, entityId: id, data: res.data })
|
|
||||||
);*/
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateWhere(
|
async updateWhere(data: Data, where?: RepoQuery["where"]): Promise<MutatorResponse<DB[TB][]>> {
|
||||||
data: EntityData,
|
|
||||||
where?: RepoQuery["where"]
|
|
||||||
): Promise<MutatorResponse<EntityData>> {
|
|
||||||
const entity = this.entity;
|
const entity = this.entity;
|
||||||
|
|
||||||
const validatedData = await this.getValidatedData(data, "update");
|
const validatedData = await this.getValidatedData(data, "update");
|
||||||
|
|
||||||
/*await this.emgr.emit(
|
|
||||||
new Mutator.Events.MutatorUpdateBefore({ entity, entityId: id, data: validatedData })
|
|
||||||
);*/
|
|
||||||
|
|
||||||
const query = this.appendWhere(this.conn.updateTable(entity.name), where)
|
const query = this.appendWhere(this.conn.updateTable(entity.name), where)
|
||||||
.set(validatedData)
|
.set(validatedData as any)
|
||||||
//.where(entity.id().name, "=", id)
|
|
||||||
.returning(entity.getSelect());
|
.returning(entity.getSelect());
|
||||||
|
|
||||||
const res = await this.many(query);
|
return (await this.many(query)) as any;
|
||||||
|
|
||||||
/*await this.emgr.emit(
|
|
||||||
new Mutator.Events.MutatorUpdateAfter({ entity, entityId: id, data: res.data })
|
|
||||||
);*/
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -272,7 +272,7 @@ export class Repository<DB = any, TB extends keyof DB = any> implements EmitsEve
|
|||||||
async findId(
|
async findId(
|
||||||
id: PrimaryFieldType,
|
id: PrimaryFieldType,
|
||||||
_options?: Partial<Omit<RepoQuery, "where" | "limit" | "offset">>
|
_options?: Partial<Omit<RepoQuery, "where" | "limit" | "offset">>
|
||||||
): Promise<RepositoryResponse<DB[TB]>> {
|
): Promise<RepositoryResponse<DB[TB] | undefined>> {
|
||||||
const { qb, options } = this.buildQuery(
|
const { qb, options } = this.buildQuery(
|
||||||
{
|
{
|
||||||
..._options,
|
..._options,
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import type { Generated } from "kysely";
|
||||||
|
import { MediaField, type MediaFieldConfig, type MediaItem } from "media/MediaField";
|
||||||
import {
|
import {
|
||||||
BooleanField,
|
BooleanField,
|
||||||
type BooleanFieldConfig,
|
type BooleanFieldConfig,
|
||||||
@@ -25,15 +27,14 @@ import {
|
|||||||
type TEntityType,
|
type TEntityType,
|
||||||
TextField,
|
TextField,
|
||||||
type TextFieldConfig
|
type TextFieldConfig
|
||||||
} from "data";
|
} from "../index";
|
||||||
import type { Generated } from "kysely";
|
|
||||||
import { MediaField, type MediaFieldConfig, type MediaItem } from "media/MediaField";
|
|
||||||
|
|
||||||
type Options<Config = any> = {
|
type Options<Config = any> = {
|
||||||
entity: { name: string; fields: Record<string, Field<any, any, any>> };
|
entity: { name: string; fields: Record<string, Field<any, any, any>> };
|
||||||
field_name: string;
|
field_name: string;
|
||||||
config: Config;
|
config: Config;
|
||||||
is_required: boolean;
|
is_required: boolean;
|
||||||
|
another?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const FieldMap = {
|
const FieldMap = {
|
||||||
@@ -239,7 +240,7 @@ export function relation<Local extends Entity>(local: Local) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
type InferEntityFields<T> = T extends Entity<infer _N, infer Fields>
|
export type InferEntityFields<T> = T extends Entity<infer _N, infer Fields>
|
||||||
? {
|
? {
|
||||||
[K in keyof Fields]: Fields[K] extends { _type: infer Type; _required: infer Required }
|
[K in keyof Fields]: Fields[K] extends { _type: infer Type; _required: infer Required }
|
||||||
? Required extends true
|
? Required extends true
|
||||||
@@ -284,12 +285,25 @@ type OptionalUndefined<
|
|||||||
}
|
}
|
||||||
>;
|
>;
|
||||||
|
|
||||||
type InferField<Field> = Field extends { _type: infer Type; _required: infer Required }
|
export type InferField<Field> = Field extends { _type: infer Type; _required: infer Required }
|
||||||
? Required extends true
|
? Required extends true
|
||||||
? Type
|
? Type
|
||||||
: Type | undefined
|
: Type | undefined
|
||||||
: never;
|
: never;
|
||||||
|
|
||||||
|
const n = number();
|
||||||
|
type T2 = InferField<typeof n>;
|
||||||
|
|
||||||
|
const users = entity("users", {
|
||||||
|
name: text(),
|
||||||
|
email: text(),
|
||||||
|
created_at: datetime(),
|
||||||
|
updated_at: datetime()
|
||||||
|
});
|
||||||
|
type TUsersFields = InferEntityFields<typeof users>;
|
||||||
|
type TUsers = Schema<typeof users>;
|
||||||
|
type TUsers2 = Simplify<OptionalUndefined<Schema<typeof users>>>;
|
||||||
|
|
||||||
export type InsertSchema<T> = Simplify<OptionalUndefined<InferEntityFields<T>>>;
|
export type InsertSchema<T> = Simplify<OptionalUndefined<InferEntityFields<T>>>;
|
||||||
export type Schema<T> = { id: Generated<number> } & InsertSchema<T>;
|
export type Schema<T> = Simplify<{ id: Generated<number> } & InsertSchema<T>>;
|
||||||
export type FieldSchema<T> = Simplify<OptionalUndefined<InferFields<T>>>;
|
export type FieldSchema<T> = Simplify<OptionalUndefined<InferFields<T>>>;
|
||||||
|
|||||||
@@ -4,8 +4,10 @@ export {
|
|||||||
getDefaultConfig,
|
getDefaultConfig,
|
||||||
getDefaultSchema,
|
getDefaultSchema,
|
||||||
type ModuleConfigs,
|
type ModuleConfigs,
|
||||||
type ModuleSchemas
|
type ModuleSchemas,
|
||||||
} from "modules/ModuleManager";
|
type ModuleManagerOptions,
|
||||||
|
type ModuleBuildContext
|
||||||
|
} from "./modules/ModuleManager";
|
||||||
|
|
||||||
export type * from "./adapter";
|
export type * from "./adapter";
|
||||||
export { Api, type ApiOptions } from "./Api";
|
export { Api, type ApiOptions } from "./Api";
|
||||||
|
|||||||
@@ -174,7 +174,7 @@ export class MediaController implements ClassController {
|
|||||||
const result = await mutator.insertOne({
|
const result = await mutator.insertOne({
|
||||||
...this.media.uploadedEventDataToMediaPayload(info),
|
...this.media.uploadedEventDataToMediaPayload(info),
|
||||||
...mediaRef
|
...mediaRef
|
||||||
});
|
} as any);
|
||||||
mutator.__unstable_toggleSystemEntityCreation(true);
|
mutator.__unstable_toggleSystemEntityCreation(true);
|
||||||
|
|
||||||
// delete items if needed
|
// delete items if needed
|
||||||
|
|||||||
@@ -5,10 +5,10 @@ import type { Static, TSchema } from "core/utils";
|
|||||||
import type { Connection, EntityManager } from "data";
|
import type { Connection, EntityManager } from "data";
|
||||||
import type { Hono } from "hono";
|
import type { Hono } from "hono";
|
||||||
|
|
||||||
export type ModuleBuildContext = {
|
export type ModuleBuildContext<DB = any> = {
|
||||||
connection: Connection;
|
connection: Connection;
|
||||||
server: Hono<any>;
|
server: Hono<any>;
|
||||||
em: EntityManager<any>;
|
em: EntityManager<DB>;
|
||||||
emgr: EventManager<any>;
|
emgr: EventManager<any>;
|
||||||
guard: Guard;
|
guard: Guard;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -35,6 +35,8 @@ import { AppFlows } from "../flows/AppFlows";
|
|||||||
import { AppMedia } from "../media/AppMedia";
|
import { AppMedia } from "../media/AppMedia";
|
||||||
import type { Module, ModuleBuildContext } from "./Module";
|
import type { Module, ModuleBuildContext } from "./Module";
|
||||||
|
|
||||||
|
export type { ModuleBuildContext };
|
||||||
|
|
||||||
export const MODULES = {
|
export const MODULES = {
|
||||||
server: AppServer,
|
server: AppServer,
|
||||||
data: AppData<any>,
|
data: AppData<any>,
|
||||||
@@ -75,7 +77,10 @@ export type ModuleManagerOptions = {
|
|||||||
) => Promise<void>;
|
) => Promise<void>;
|
||||||
// base path for the hono instance
|
// base path for the hono instance
|
||||||
basePath?: string;
|
basePath?: string;
|
||||||
|
// doesn't perform validity checks for given/fetched config
|
||||||
trustFetched?: boolean;
|
trustFetched?: boolean;
|
||||||
|
// runs when initial config provided on a fresh database
|
||||||
|
seed?: (ctx: ModuleBuildContext) => Promise<void>;
|
||||||
};
|
};
|
||||||
|
|
||||||
type ConfigTable<Json = ModuleConfigs> = {
|
type ConfigTable<Json = ModuleConfigs> = {
|
||||||
@@ -294,7 +299,7 @@ export class ModuleManager {
|
|||||||
version,
|
version,
|
||||||
json: configs,
|
json: configs,
|
||||||
updated_at: new Date()
|
updated_at: new Date()
|
||||||
},
|
} as any,
|
||||||
{
|
{
|
||||||
type: "config",
|
type: "config",
|
||||||
version
|
version
|
||||||
@@ -448,6 +453,9 @@ export class ModuleManager {
|
|||||||
await this.buildModules();
|
await this.buildModules();
|
||||||
await this.save();
|
await this.save();
|
||||||
|
|
||||||
|
// run initial setup
|
||||||
|
await this.setupInitial();
|
||||||
|
|
||||||
this.logger.clear();
|
this.logger.clear();
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
@@ -462,6 +470,18 @@ export class ModuleManager {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected async setupInitial() {
|
||||||
|
const ctx = {
|
||||||
|
...this.ctx(),
|
||||||
|
// disable events for initial setup
|
||||||
|
em: this.ctx().em.fork()
|
||||||
|
};
|
||||||
|
|
||||||
|
// perform a sync
|
||||||
|
await ctx.em.schema().sync({ force: true });
|
||||||
|
await this.options?.seed?.(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
get<K extends keyof Modules>(key: K): Modules[K] {
|
get<K extends keyof Modules>(key: K): Modules[K] {
|
||||||
if (!(key in this.modules)) {
|
if (!(key in this.modules)) {
|
||||||
throw new Error(`Module "${key}" doesn't exist, cannot get`);
|
throw new Error(`Module "${key}" doesn't exist, cannot get`);
|
||||||
|
|||||||
@@ -5,14 +5,14 @@ import { useApi } from "ui/client";
|
|||||||
|
|
||||||
export const useApiQuery = <
|
export const useApiQuery = <
|
||||||
Data,
|
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>,
|
fn: (api: Api) => FetchPromise<Data>,
|
||||||
options?: SWRConfiguration & { enabled?: boolean; refine?: RefineFn }
|
options?: SWRConfiguration & { enabled?: boolean; refine?: RefineFn }
|
||||||
) => {
|
) => {
|
||||||
const api = useApi();
|
const api = useApi();
|
||||||
const promise = fn(api);
|
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 fetcher = () => promise.execute().then(refine);
|
||||||
const key = promise.key();
|
const key = promise.key();
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import type { PrimaryFieldType } from "core";
|
import type { PrimaryFieldType } from "core";
|
||||||
import { objectTransform } from "core/utils";
|
import { objectTransform } from "core/utils";
|
||||||
import type { EntityData, RepoQuery } from "data";
|
import type { EntityData, RepoQuery } from "data";
|
||||||
import type { ResponseObject } from "modules/ModuleApi";
|
import type { ModuleApi, ResponseObject } from "modules/ModuleApi";
|
||||||
import useSWR, { type SWRConfiguration } from "swr";
|
import useSWR, { type SWRConfiguration, useSWRConfig } from "swr";
|
||||||
import { useApi } from "ui/client";
|
import { useApi } from "ui/client";
|
||||||
|
|
||||||
export class UseEntityApiError<Payload = any> extends Error {
|
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 = <
|
export const useEntity = <
|
||||||
Entity extends string,
|
Entity extends keyof DB | string,
|
||||||
Id extends PrimaryFieldType | undefined = undefined
|
Id extends PrimaryFieldType | undefined = undefined,
|
||||||
|
Data = Entity extends keyof DB ? DB[Entity] : EntityData
|
||||||
>(
|
>(
|
||||||
entity: Entity,
|
entity: Entity,
|
||||||
id?: Id
|
id?: Id
|
||||||
@@ -25,7 +35,7 @@ export const useEntity = <
|
|||||||
const api = useApi().data;
|
const api = useApi().data;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
create: async (input: EntityData) => {
|
create: async (input: Omit<Data, "id">) => {
|
||||||
const res = await api.createOne(entity, input);
|
const res = await api.createOne(entity, input);
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
throw new UseEntityApiError(res.data, res.res, "Failed to create entity");
|
throw new UseEntityApiError(res.data, res.res, "Failed to create entity");
|
||||||
@@ -37,9 +47,12 @@ export const useEntity = <
|
|||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
throw new UseEntityApiError(res.data, res.res, "Failed to read entity");
|
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) {
|
if (!_id) {
|
||||||
throw new Error("id is required");
|
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 = <
|
export const useEntityQuery = <
|
||||||
Entity extends string,
|
Entity extends keyof DB | string,
|
||||||
Id extends PrimaryFieldType | undefined = undefined
|
Id extends PrimaryFieldType | undefined = undefined
|
||||||
>(
|
>(
|
||||||
entity: Entity,
|
entity: Entity,
|
||||||
@@ -72,28 +94,28 @@ export const useEntityQuery = <
|
|||||||
query?: Partial<RepoQuery>,
|
query?: Partial<RepoQuery>,
|
||||||
options?: SWRConfiguration & { enabled?: boolean }
|
options?: SWRConfiguration & { enabled?: boolean }
|
||||||
) => {
|
) => {
|
||||||
|
const { mutate } = useSWRConfig();
|
||||||
const api = useApi().data;
|
const api = useApi().data;
|
||||||
const key =
|
const key = makeKey(api, entity, id);
|
||||||
options?.enabled !== false
|
const { read, ...actions } = useEntity<Entity, Id>(entity, id);
|
||||||
? [...(api.options?.basepath?.split("/") ?? []), entity, ...(id ? [id] : [])].filter(
|
|
||||||
Boolean
|
|
||||||
)
|
|
||||||
: null;
|
|
||||||
const { read, ...actions } = useEntity(entity, id) as any;
|
|
||||||
const fetcher = () => read(query);
|
const fetcher = () => read(query);
|
||||||
|
|
||||||
type T = Awaited<ReturnType<(typeof api)[Id extends undefined ? "readMany" : "readOne"]>>;
|
type T = Awaited<ReturnType<typeof fetcher>>;
|
||||||
const swr = useSWR<T>(key, fetcher, {
|
const swr = useSWR<T>(options?.enabled === false ? null : key, fetcher as any, {
|
||||||
revalidateOnFocus: false,
|
revalidateOnFocus: false,
|
||||||
keepPreviousData: false,
|
keepPreviousData: false,
|
||||||
...options
|
...options
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapped = objectTransform(actions, (action) => {
|
const mapped = objectTransform(actions, (action) => {
|
||||||
if (action === "read") return;
|
return async (...args: any) => {
|
||||||
|
// @ts-ignore
|
||||||
|
const res = await action(...args);
|
||||||
|
|
||||||
return async (...args) => {
|
// mutate the key + list key
|
||||||
return swr.mutate(action(...args)) as any;
|
mutate(key);
|
||||||
|
if (id) mutate(makeKey(api, entity));
|
||||||
|
return res;
|
||||||
};
|
};
|
||||||
}) as Omit<ReturnType<typeof useEntity<Entity, Id>>, "read">;
|
}) as Omit<ReturnType<typeof useEntity<Entity, Id>>, "read">;
|
||||||
|
|
||||||
@@ -106,14 +128,14 @@ export const useEntityQuery = <
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const useEntityMutate = <
|
export const useEntityMutate = <
|
||||||
Entity extends string,
|
Entity extends keyof DB | string,
|
||||||
Id extends PrimaryFieldType | undefined = undefined
|
Id extends PrimaryFieldType | undefined = undefined
|
||||||
>(
|
>(
|
||||||
entity: Entity,
|
entity: Entity,
|
||||||
id?: Id,
|
id?: Id,
|
||||||
options?: SWRConfiguration
|
options?: SWRConfiguration
|
||||||
) => {
|
) => {
|
||||||
const { data, ...$q } = useEntityQuery(entity, id, undefined, {
|
const { data, ...$q } = useEntityQuery<Entity, Id>(entity, id, undefined, {
|
||||||
...options,
|
...options,
|
||||||
enabled: false
|
enabled: false
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import type { ReactCodeMirrorProps } from "@uiw/react-codemirror";
|
import { default as CodeMirror, type ReactCodeMirrorProps } from "@uiw/react-codemirror";
|
||||||
import { Suspense, lazy } from "react";
|
|
||||||
import { useBknd } from "ui/client/bknd";
|
import { useBknd } from "ui/client/bknd";
|
||||||
const CodeMirror = lazy(() => import("@uiw/react-codemirror"));
|
|
||||||
|
|
||||||
export default function CodeEditor({ editable, basicSetup, ...props }: ReactCodeMirrorProps) {
|
export default function CodeEditor({ editable, basicSetup, ...props }: ReactCodeMirrorProps) {
|
||||||
const b = useBknd();
|
const b = useBknd();
|
||||||
@@ -15,13 +14,11 @@ export default function CodeEditor({ editable, basicSetup, ...props }: ReactCode
|
|||||||
: basicSetup;
|
: basicSetup;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Suspense>
|
<CodeMirror
|
||||||
<CodeMirror
|
theme={theme === "dark" ? "dark" : "light"}
|
||||||
theme={theme === "dark" ? "dark" : "light"}
|
editable={editable}
|
||||||
editable={editable}
|
basicSetup={_basicSetup}
|
||||||
basicSetup={_basicSetup}
|
{...props}
|
||||||
{...props}
|
/>
|
||||||
/>
|
|
||||||
</Suspense>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,12 @@
|
|||||||
import type { Schema } from "@cfworker/json-schema";
|
import type { Schema } from "@cfworker/json-schema";
|
||||||
import Form from "@rjsf/core";
|
import Form from "@rjsf/core";
|
||||||
import type { RJSFSchema, UiSchema } from "@rjsf/utils";
|
import type { RJSFSchema, UiSchema } from "@rjsf/utils";
|
||||||
|
import { cloneDeep } from "lodash-es";
|
||||||
import { forwardRef, useId, useImperativeHandle, useRef, useState } from "react";
|
import { forwardRef, useId, useImperativeHandle, useRef, useState } from "react";
|
||||||
//import { JsonSchemaValidator } from "./JsonSchemaValidator";
|
|
||||||
import { fields as Fields } from "./fields";
|
import { fields as Fields } from "./fields";
|
||||||
import { templates as Templates } from "./templates";
|
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 { RJSFTypeboxValidator } from "./typebox/RJSFTypeboxValidator";
|
||||||
|
import { widgets as Widgets } from "./widgets";
|
||||||
|
|
||||||
const validator = new RJSFTypeboxValidator();
|
const validator = new RJSFTypeboxValidator();
|
||||||
|
|
||||||
|
|||||||
18
app/src/ui/components/form/json-schema/index.tsx
Normal file
18
app/src/ui/components/form/json-schema/index.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
});
|
||||||
@@ -4,7 +4,7 @@ import {
|
|||||||
JsonSchemaForm,
|
JsonSchemaForm,
|
||||||
type JsonSchemaFormProps,
|
type JsonSchemaFormProps,
|
||||||
type JsonSchemaFormRef
|
type JsonSchemaFormRef
|
||||||
} from "ui/components/form/json-schema/JsonSchemaForm";
|
} from "ui/components/form/json-schema";
|
||||||
|
|
||||||
import type { ContextModalProps } from "@mantine/modals";
|
import type { ContextModalProps } from "@mantine/modals";
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,8 @@
|
|||||||
import type { FieldApi } from "@tanstack/react-form";
|
import type { FieldApi } from "@tanstack/react-form";
|
||||||
import type { EntityData, JsonSchemaField } from "data";
|
import type { EntityData, JsonSchemaField } from "data";
|
||||||
import { Suspense, lazy } from "react";
|
|
||||||
import * as Formy from "ui/components/form/Formy";
|
import * as Formy from "ui/components/form/Formy";
|
||||||
import { FieldLabel } from "ui/components/form/Formy";
|
import { FieldLabel } from "ui/components/form/Formy";
|
||||||
|
import { JsonSchemaForm } from "ui/components/form/json-schema";
|
||||||
const JsonSchemaForm = lazy(() =>
|
|
||||||
import("ui/components/form/json-schema/JsonSchemaForm").then((m) => ({
|
|
||||||
default: m.JsonSchemaForm
|
|
||||||
}))
|
|
||||||
);
|
|
||||||
|
|
||||||
export function EntityJsonSchemaFormField({
|
export function EntityJsonSchemaFormField({
|
||||||
fieldApi,
|
fieldApi,
|
||||||
@@ -34,23 +28,21 @@ export function EntityJsonSchemaFormField({
|
|||||||
return (
|
return (
|
||||||
<Formy.Group>
|
<Formy.Group>
|
||||||
<FieldLabel htmlFor={fieldApi.name} field={field} />
|
<FieldLabel htmlFor={fieldApi.name} field={field} />
|
||||||
<Suspense fallback={<div>Loading...</div>}>
|
<div
|
||||||
<div
|
data-disabled={disabled ? 1 : undefined}
|
||||||
data-disabled={disabled ? 1 : undefined}
|
className="data-[disabled]:opacity-70 data-[disabled]:pointer-events-none"
|
||||||
className="data-[disabled]:opacity-70 data-[disabled]:pointer-events-none"
|
>
|
||||||
>
|
<JsonSchemaForm
|
||||||
<JsonSchemaForm
|
schema={field.getJsonSchema()}
|
||||||
schema={field.getJsonSchema()}
|
onChange={handleChange}
|
||||||
onChange={handleChange}
|
direction="horizontal"
|
||||||
direction="horizontal"
|
formData={formData}
|
||||||
formData={formData}
|
uiSchema={{
|
||||||
uiSchema={{
|
"ui:globalOptions": { flexDirection: "row" },
|
||||||
"ui:globalOptions": { flexDirection: "row" },
|
...field.getJsonUiSchema()
|
||||||
...field.getJsonUiSchema()
|
}}
|
||||||
}}
|
/>
|
||||||
/>
|
</div>
|
||||||
</div>
|
|
||||||
</Suspense>
|
|
||||||
</Formy.Group>
|
</Formy.Group>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,9 @@
|
|||||||
import { Handle, type Node, type NodeProps, Position } from "@xyflow/react";
|
import { Handle, type Node, type NodeProps, Position } from "@xyflow/react";
|
||||||
import { Const, Type, transformObject } from "core/utils";
|
import { Const, Type, transformObject } from "core/utils";
|
||||||
import { type TaskRenderProps, type Trigger, TriggerMap } from "flows";
|
import { type Trigger, TriggerMap } from "flows";
|
||||||
import { Suspense, lazy } from "react";
|
|
||||||
import type { IconType } from "react-icons";
|
import type { IconType } from "react-icons";
|
||||||
import { TbCircleLetterT } from "react-icons/tb";
|
import { TbCircleLetterT } from "react-icons/tb";
|
||||||
const JsonSchemaForm = lazy(() =>
|
import { JsonSchemaForm } from "ui/components/form/json-schema";
|
||||||
import("ui/components/form/json-schema/JsonSchemaForm").then((m) => ({
|
|
||||||
default: m.JsonSchemaForm
|
|
||||||
}))
|
|
||||||
);
|
|
||||||
|
|
||||||
export type TaskComponentProps = NodeProps<Node<{ trigger: Trigger }>> & {
|
export type TaskComponentProps = NodeProps<Node<{ trigger: Trigger }>> & {
|
||||||
Icon?: IconType;
|
Icon?: IconType;
|
||||||
@@ -48,17 +43,15 @@ export function TriggerComponent({
|
|||||||
</div>
|
</div>
|
||||||
<div className="w-full h-px bg-primary/10" />
|
<div className="w-full h-px bg-primary/10" />
|
||||||
<div className="flex flex-col gap-2 px-3 py-2">
|
<div className="flex flex-col gap-2 px-3 py-2">
|
||||||
<Suspense fallback={<div>Loading...</div>}>
|
<JsonSchemaForm
|
||||||
<JsonSchemaForm
|
className="legacy"
|
||||||
className="legacy"
|
schema={Type.Union(triggerSchemas)}
|
||||||
schema={Type.Union(triggerSchemas)}
|
onChange={console.log}
|
||||||
onChange={console.log}
|
formData={trigger}
|
||||||
formData={trigger}
|
{...props}
|
||||||
{...props}
|
/*uiSchema={uiSchema}*/
|
||||||
/*uiSchema={uiSchema}*/
|
/*fields={{ template: TemplateField }}*/
|
||||||
/*fields={{ template: TemplateField }}*/
|
/>
|
||||||
/>
|
|
||||||
</Suspense>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Handle
|
<Handle
|
||||||
|
|||||||
@@ -1,12 +1,5 @@
|
|||||||
import type { Task } from "flows";
|
import type { Task } from "flows";
|
||||||
import { Suspense, lazy } from "react";
|
import { JsonSchemaForm } from "ui/components/form/json-schema";
|
||||||
import { TemplateField } from "./TemplateField";
|
|
||||||
|
|
||||||
const JsonSchemaForm = lazy(() =>
|
|
||||||
import("ui/components/form/json-schema/JsonSchemaForm").then((m) => ({
|
|
||||||
default: m.JsonSchemaForm
|
|
||||||
}))
|
|
||||||
);
|
|
||||||
|
|
||||||
export type TaskFormProps = {
|
export type TaskFormProps = {
|
||||||
task: Task;
|
task: Task;
|
||||||
@@ -26,16 +19,14 @@ export function TaskForm({ task, onChange, ...props }: TaskFormProps) {
|
|||||||
//console.log("uiSchema", uiSchema);
|
//console.log("uiSchema", uiSchema);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Suspense fallback={<div>Loading...</div>}>
|
<JsonSchemaForm
|
||||||
<JsonSchemaForm
|
className="legacy"
|
||||||
className="legacy"
|
schema={schema}
|
||||||
schema={schema}
|
onChange={onChange}
|
||||||
onChange={onChange}
|
formData={params}
|
||||||
formData={params}
|
{...props}
|
||||||
{...props}
|
/*uiSchema={uiSchema}*/
|
||||||
/*uiSchema={uiSchema}*/
|
/*fields={{ template: TemplateField }}*/
|
||||||
/*fields={{ template: TemplateField }}*/
|
/>
|
||||||
/>
|
|
||||||
</Suspense>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 { useBkndData } from "ui/client/schema/data/use-bknd-data";
|
||||||
import { Button } from "ui/components/buttons/Button";
|
import { Button } from "ui/components/buttons/Button";
|
||||||
import { Alert } from "ui/components/display/Alert";
|
import { Alert } from "ui/components/display/Alert";
|
||||||
import {
|
import { JsonSchemaForm, type JsonSchemaFormRef } from "ui/components/form/json-schema";
|
||||||
JsonSchemaForm,
|
|
||||||
type JsonSchemaFormRef
|
|
||||||
} from "ui/components/form/json-schema/JsonSchemaForm";
|
|
||||||
import * as AppShell from "ui/layouts/AppShell/AppShell";
|
import * as AppShell from "ui/layouts/AppShell/AppShell";
|
||||||
import { useNavigate } from "ui/lib/routes";
|
import { useNavigate } from "ui/lib/routes";
|
||||||
import { extractSchema } from "../settings/utils/schema";
|
import { extractSchema } from "../settings/utils/schema";
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
import { cloneDeep, omit } from "lodash-es";
|
import { cloneDeep, omit } from "lodash-es";
|
||||||
import { useBknd } from "ui/client/bknd";
|
import { useBknd } from "ui/client/bknd";
|
||||||
import { Button } from "ui/components/buttons/Button";
|
import { Button } from "ui/components/buttons/Button";
|
||||||
import { JsonSchemaForm } from "ui/components/form/json-schema/JsonSchemaForm";
|
|
||||||
import * as AppShell from "../../layouts/AppShell/AppShell";
|
import * as AppShell from "../../layouts/AppShell/AppShell";
|
||||||
import { extractSchema } from "../settings/utils/schema";
|
|
||||||
|
|
||||||
export function AuthStrategiesList() {
|
export function AuthStrategiesList() {
|
||||||
useBknd({ withSecrets: true });
|
useBknd({ withSecrets: true });
|
||||||
|
|||||||
@@ -13,10 +13,7 @@ import { useBkndData } from "ui/client/schema/data/use-bknd-data";
|
|||||||
import { Button } from "ui/components/buttons/Button";
|
import { Button } from "ui/components/buttons/Button";
|
||||||
import { IconButton } from "ui/components/buttons/IconButton";
|
import { IconButton } from "ui/components/buttons/IconButton";
|
||||||
import { Empty } from "ui/components/display/Empty";
|
import { Empty } from "ui/components/display/Empty";
|
||||||
import {
|
import { JsonSchemaForm, type JsonSchemaFormRef } from "ui/components/form/json-schema";
|
||||||
JsonSchemaForm,
|
|
||||||
type JsonSchemaFormRef
|
|
||||||
} from "ui/components/form/json-schema/JsonSchemaForm";
|
|
||||||
import { Dropdown } from "ui/components/overlay/Dropdown";
|
import { Dropdown } from "ui/components/overlay/Dropdown";
|
||||||
import * as AppShell from "ui/layouts/AppShell/AppShell";
|
import * as AppShell from "ui/layouts/AppShell/AppShell";
|
||||||
import { Breadcrumbs2 } from "ui/layouts/AppShell/Breadcrumbs2";
|
import { Breadcrumbs2 } from "ui/layouts/AppShell/Breadcrumbs2";
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ import { Button } from "ui/components/buttons/Button";
|
|||||||
import { IconButton } from "ui/components/buttons/IconButton";
|
import { IconButton } from "ui/components/buttons/IconButton";
|
||||||
import { JsonViewer } from "ui/components/code/JsonViewer";
|
import { JsonViewer } from "ui/components/code/JsonViewer";
|
||||||
import { MantineSwitch } from "ui/components/form/hook-form-mantine/MantineSwitch";
|
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 { type SortableItemProps, SortableList } from "ui/components/list/SortableList";
|
||||||
import { Popover } from "ui/components/overlay/Popover";
|
import { Popover } from "ui/components/overlay/Popover";
|
||||||
import { fieldSpecs } from "ui/modules/data/components/fields-specs";
|
import { fieldSpecs } from "ui/modules/data/components/fields-specs";
|
||||||
|
|||||||
@@ -1,14 +1,19 @@
|
|||||||
import { Suspense, lazy } from "react";
|
import { Suspense, lazy } from "react";
|
||||||
import { useBknd } from "ui/client/bknd";
|
import { useBknd } from "ui/client/bknd";
|
||||||
import { Route, Router, Switch } from "wouter";
|
import { Route, Router, Switch } from "wouter";
|
||||||
|
import AuthRoutes from "./auth";
|
||||||
import { AuthLogin } from "./auth/auth.login";
|
import { AuthLogin } from "./auth/auth.login";
|
||||||
|
import DataRoutes from "./data";
|
||||||
|
import FlowRoutes from "./flows";
|
||||||
|
import MediaRoutes from "./media";
|
||||||
import { Root, RootEmpty } from "./root";
|
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 AuthRoutes = lazy(() => import("./auth"));
|
||||||
const MediaRoutes = lazy(() => import("./media"));
|
const MediaRoutes = lazy(() => import("./media"));
|
||||||
const FlowRoutes = lazy(() => import("./flows"));
|
const FlowRoutes = lazy(() => import("./flows"));
|
||||||
const SettingsRoutes = lazy(() => import("./settings"));
|
const SettingsRoutes = lazy(() => import("./settings"));*/
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const TestRoutes = lazy(() => import("./test"));
|
const TestRoutes = lazy(() => import("./test"));
|
||||||
|
|||||||
@@ -8,10 +8,7 @@ import { Button } from "ui/components/buttons/Button";
|
|||||||
import { IconButton } from "ui/components/buttons/IconButton";
|
import { IconButton } from "ui/components/buttons/IconButton";
|
||||||
import { Alert } from "ui/components/display/Alert";
|
import { Alert } from "ui/components/display/Alert";
|
||||||
import { Empty } from "ui/components/display/Empty";
|
import { Empty } from "ui/components/display/Empty";
|
||||||
import {
|
import { JsonSchemaForm, type JsonSchemaFormRef } from "ui/components/form/json-schema";
|
||||||
JsonSchemaForm,
|
|
||||||
type JsonSchemaFormRef
|
|
||||||
} from "ui/components/form/json-schema/JsonSchemaForm";
|
|
||||||
import { Dropdown } from "ui/components/overlay/Dropdown";
|
import { Dropdown } from "ui/components/overlay/Dropdown";
|
||||||
import { DataTable } from "ui/components/table/DataTable";
|
import { DataTable } from "ui/components/table/DataTable";
|
||||||
import { useEvent } from "ui/hooks/use-event";
|
import { useEvent } from "ui/hooks/use-event";
|
||||||
|
|||||||
@@ -3,16 +3,13 @@ import type { TObject } from "core/utils";
|
|||||||
import { omit } from "lodash-es";
|
import { omit } from "lodash-es";
|
||||||
import { useRef, useState } from "react";
|
import { useRef, useState } from "react";
|
||||||
import { TbCirclePlus, TbVariable } from "react-icons/tb";
|
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 { 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 = {
|
export type SettingsNewModalProps = {
|
||||||
schema: TObject;
|
schema: TObject;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { parse } from "core/utils";
|
|||||||
import { AppFlows } from "flows/AppFlows";
|
import { AppFlows } from "flows/AppFlows";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { JsonViewer } from "../../../components/code/JsonViewer";
|
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";
|
import { Scrollable } from "../../../layouts/AppShell/AppShell";
|
||||||
|
|
||||||
export default function FlowCreateSchemaTest() {
|
export default function FlowCreateSchemaTest() {
|
||||||
|
|||||||
@@ -2,12 +2,9 @@ import Form from "@rjsf/core";
|
|||||||
import type { RJSFSchema, UiSchema } from "@rjsf/utils";
|
import type { RJSFSchema, UiSchema } from "@rjsf/utils";
|
||||||
import { useRef } from "react";
|
import { useRef } from "react";
|
||||||
import { TbPlus, TbTrash } from "react-icons/tb";
|
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 * as Formy from "../../../../components/form/Formy";
|
||||||
import {
|
|
||||||
JsonSchemaForm,
|
|
||||||
type JsonSchemaFormRef
|
|
||||||
} from "../../../../components/form/json-schema/JsonSchemaForm";
|
|
||||||
import * as AppShell from "../../../../layouts/AppShell/AppShell";
|
import * as AppShell from "../../../../layouts/AppShell/AppShell";
|
||||||
|
|
||||||
class CfJsonSchemaValidator {}
|
class CfJsonSchemaValidator {}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import type { Schema } from "@cfworker/json-schema";
|
import type { Schema } from "@cfworker/json-schema";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { JsonSchemaForm } from "../../../components/form/json-schema/JsonSchemaForm";
|
import { JsonSchemaForm } from "ui/components/form/json-schema";
|
||||||
import { Scrollable } from "../../../layouts/AppShell/AppShell";
|
import { Scrollable } from "ui/layouts/AppShell/AppShell";
|
||||||
|
|
||||||
const schema: Schema = {
|
const schema: Schema = {
|
||||||
definitions: {
|
definitions: {
|
||||||
@@ -9,52 +9,52 @@ const schema: Schema = {
|
|||||||
anyOf: [
|
anyOf: [
|
||||||
{
|
{
|
||||||
title: "String",
|
title: "String",
|
||||||
type: "string",
|
type: "string"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Number",
|
title: "Number",
|
||||||
type: "number",
|
type: "number"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Boolean",
|
title: "Boolean",
|
||||||
type: "boolean",
|
type: "boolean"
|
||||||
},
|
}
|
||||||
],
|
]
|
||||||
},
|
},
|
||||||
numeric: {
|
numeric: {
|
||||||
anyOf: [
|
anyOf: [
|
||||||
{
|
{
|
||||||
title: "Number",
|
title: "Number",
|
||||||
type: "number",
|
type: "number"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Datetime",
|
title: "Datetime",
|
||||||
type: "string",
|
type: "string",
|
||||||
format: "date-time",
|
format: "date-time"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Date",
|
title: "Date",
|
||||||
type: "string",
|
type: "string",
|
||||||
format: "date",
|
format: "date"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Time",
|
title: "Time",
|
||||||
type: "string",
|
type: "string",
|
||||||
format: "time",
|
format: "time"
|
||||||
},
|
}
|
||||||
],
|
]
|
||||||
},
|
},
|
||||||
boolean: {
|
boolean: {
|
||||||
title: "Boolean",
|
title: "Boolean",
|
||||||
type: "boolean",
|
type: "boolean"
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
type: "object",
|
type: "object",
|
||||||
properties: {
|
properties: {
|
||||||
operand: {
|
operand: {
|
||||||
enum: ["$and", "$or"],
|
enum: ["$and", "$or"],
|
||||||
default: "$and",
|
default: "$and",
|
||||||
type: "string",
|
type: "string"
|
||||||
},
|
},
|
||||||
conditions: {
|
conditions: {
|
||||||
type: "array",
|
type: "array",
|
||||||
@@ -64,10 +64,10 @@ const schema: Schema = {
|
|||||||
operand: {
|
operand: {
|
||||||
enum: ["$and", "$or"],
|
enum: ["$and", "$or"],
|
||||||
default: "$and",
|
default: "$and",
|
||||||
type: "string",
|
type: "string"
|
||||||
},
|
},
|
||||||
key: {
|
key: {
|
||||||
type: "string",
|
type: "string"
|
||||||
},
|
},
|
||||||
operator: {
|
operator: {
|
||||||
type: "array",
|
type: "array",
|
||||||
@@ -78,30 +78,30 @@ const schema: Schema = {
|
|||||||
type: "object",
|
type: "object",
|
||||||
properties: {
|
properties: {
|
||||||
$eq: {
|
$eq: {
|
||||||
$ref: "#/definitions/primitive",
|
$ref: "#/definitions/primitive"
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
required: ["$eq"],
|
required: ["$eq"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Lower than",
|
title: "Lower than",
|
||||||
type: "object",
|
type: "object",
|
||||||
properties: {
|
properties: {
|
||||||
$lt: {
|
$lt: {
|
||||||
$ref: "#/definitions/numeric",
|
$ref: "#/definitions/numeric"
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
required: ["$lt"],
|
required: ["$lt"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Greather than",
|
title: "Greather than",
|
||||||
type: "object",
|
type: "object",
|
||||||
properties: {
|
properties: {
|
||||||
$gt: {
|
$gt: {
|
||||||
$ref: "#/definitions/numeric",
|
$ref: "#/definitions/numeric"
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
required: ["$gt"],
|
required: ["$gt"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Between",
|
title: "Between",
|
||||||
@@ -110,13 +110,13 @@ const schema: Schema = {
|
|||||||
$between: {
|
$between: {
|
||||||
type: "array",
|
type: "array",
|
||||||
items: {
|
items: {
|
||||||
$ref: "#/definitions/numeric",
|
$ref: "#/definitions/numeric"
|
||||||
},
|
},
|
||||||
minItems: 2,
|
minItems: 2,
|
||||||
maxItems: 2,
|
maxItems: 2
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
required: ["$between"],
|
required: ["$between"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "In",
|
title: "In",
|
||||||
@@ -125,23 +125,23 @@ const schema: Schema = {
|
|||||||
$in: {
|
$in: {
|
||||||
type: "array",
|
type: "array",
|
||||||
items: {
|
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() {
|
export default function QueryJsonFormTest() {
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { twMerge } from "tailwind-merge";
|
import { twMerge } from "tailwind-merge";
|
||||||
import { useBknd } from "../../../client/BkndProvider";
|
import { useBknd } from "ui/client/BkndProvider";
|
||||||
import { JsonSchemaForm } from "../../../components/form/json-schema/JsonSchemaForm";
|
import { JsonSchemaForm } from "ui/components/form/json-schema";
|
||||||
import { Scrollable } from "../../../layouts/AppShell/AppShell";
|
import { Scrollable } from "ui/layouts/AppShell/AppShell";
|
||||||
|
|
||||||
function useSchema() {
|
function useSchema() {
|
||||||
const [schema, setSchema] = useState<any>();
|
const [schema, setSchema] = useState<any>();
|
||||||
|
|||||||
@@ -1,7 +1,20 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useApiQuery } from "ui/client";
|
import { useApi, useApiQuery } from "ui/client";
|
||||||
import { Scrollable } from "ui/layouts/AppShell/AppShell";
|
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() {
|
export default function SWRAndAPI() {
|
||||||
const [text, setText] = useState("");
|
const [text, setText] = useState("");
|
||||||
const { data, ...r } = useApiQuery((api) => api.data.readOne("comments", 1), {
|
const { data, ...r } = useApiQuery((api) => api.data.readOne("comments", 1), {
|
||||||
@@ -16,7 +29,7 @@ export default function SWRAndAPI() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Scrollable>
|
<Scrollable>
|
||||||
<pre>{JSON.stringify(r.promise.keyArray({ search: false }))}</pre>
|
<pre>{JSON.stringify(r.key)}</pre>
|
||||||
{r.error && <div>failed to load</div>}
|
{r.error && <div>failed to load</div>}
|
||||||
{r.isLoading && <div>loading...</div>}
|
{r.isLoading && <div>loading...</div>}
|
||||||
{data && <pre>{JSON.stringify(data, null, 2)}</pre>}
|
{data && <pre>{JSON.stringify(data, null, 2)}</pre>}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export default function SwrAndDataApi() {
|
|||||||
|
|
||||||
function QueryDataApi() {
|
function QueryDataApi() {
|
||||||
const [text, setText] = useState("");
|
const [text, setText] = useState("");
|
||||||
const { data, update, ...r } = useEntityQuery("comments", 1, {});
|
const { data, update, ...r } = useEntityQuery("comments", 2);
|
||||||
const comment = data ? data : null;
|
const comment = data ? data : null;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -45,10 +45,10 @@ function QueryDataApi() {
|
|||||||
|
|
||||||
function DirectDataApi() {
|
function DirectDataApi() {
|
||||||
const [data, setData] = useState<any>();
|
const [data, setData] = useState<any>();
|
||||||
const { create, read, update, _delete } = useEntity("comments", 1);
|
const { create, read, update, _delete } = useEntity("users");
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
read().then(setData);
|
read().then((data) => setData(data));
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return <pre>{JSON.stringify(data, null, 2)}</pre>;
|
return <pre>{JSON.stringify(data, null, 2)}</pre>;
|
||||||
|
|||||||
@@ -26,14 +26,13 @@
|
|||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"rootDir": "./src",
|
"rootDir": "./src",
|
||||||
"outDir": "./dist",
|
"outDir": "./dist/types",
|
||||||
"tsBuildInfoFile": "./dist/tsconfig.tsbuildinfo",
|
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"paths": {
|
"paths": {
|
||||||
"*": ["./src/*"],
|
"*": ["./src/*"],
|
||||||
"bknd": ["./src/*"]
|
"bknd": ["./src/*"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"include": ["./src/**/*.ts", "./src/**/*.tsx", "./env.d.ts"],
|
"include": ["./src/**/*.ts", "./src/**/*.tsx"],
|
||||||
"exclude": ["node_modules", "dist/**/*", "../examples/bun"]
|
"exclude": ["node_modules", "dist", "dist/types", "**/*.d.ts"]
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user