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

@@ -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());
}); });

View File

@@ -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();
}
}; };
} }

View File

@@ -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"
}, },

View File

@@ -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;

View File

@@ -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;
} }

View File

@@ -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

View File

@@ -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"]) {

View File

@@ -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
); );
} }

View File

@@ -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,

View File

@@ -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) {

View File

@@ -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;
} }
} }

View File

@@ -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,

View File

@@ -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>>>;

View File

@@ -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";

View File

@@ -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

View File

@@ -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;
}; };

View File

@@ -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`);

View File

@@ -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();

View File

@@ -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
}); });

View File

@@ -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>
); );
} }

View File

@@ -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();

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, 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";

View File

@@ -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>
); );
} }

View File

@@ -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

View File

@@ -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>
); );
} }

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 { 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";

View File

@@ -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 });

View File

@@ -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";

View File

@@ -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";

View File

@@ -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"));

View File

@@ -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";

View File

@@ -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;

View File

@@ -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() {

View File

@@ -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 {}

View File

@@ -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() {

View File

@@ -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>();

View File

@@ -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>}

View File

@@ -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>;

View File

@@ -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"]
} }

BIN
bun.lockb

Binary file not shown.