diff --git a/app/__test__/data/prototype.test.ts b/app/__test__/data/prototype.test.ts index e5d3753..9d3eebd 100644 --- a/app/__test__/data/prototype.test.ts +++ b/app/__test__/data/prototype.test.ts @@ -3,6 +3,7 @@ import { BooleanField, DateField, Entity, + EntityManager, EnumField, JsonField, ManyToManyRelation, @@ -46,12 +47,17 @@ describe("prototype", () => { }); test("...2", async () => { - const user = entity("users", { - name: text().required(), + const users = entity("users", { + name: text(), bio: text(), age: number(), - some: number().required() + some: number() }); + type db = { + users: Schema; + }; + + const obj: Schema = {} as any; //console.log("user", user.toJSON()); }); diff --git a/app/build.ts b/app/build.ts index 60251f8..6511124 100644 --- a/app/build.ts +++ b/app/build.ts @@ -9,16 +9,44 @@ const watch = args.includes("--watch"); const minify = args.includes("--minify"); const types = args.includes("--types"); 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"], { onExit: () => { 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 * Using esbuild because tsup doesn't include "react" @@ -46,7 +74,8 @@ const result = await esbuild.build({ __isDev: "0", "process.env.NODE_ENV": '"production"' }, - chunkNames: "chunks/[name]-[hash]" + chunkNames: "chunks/[name]-[hash]", + logLevel: "error" }); // Write manifest @@ -96,6 +125,9 @@ await tsup.build({ treeshake: true, loader: { ".svg": "dataurl" + }, + onSuccess: async () => { + delayTypes(); } }); @@ -117,11 +149,12 @@ await tsup.build({ loader: { ".svg": "dataurl" }, - onSuccess: async () => { - console.log("--- ui built"); - }, esbuildOptions: (options) => { + options.logLevel = "silent"; options.chunkNames = "chunks/[name]-[hash]"; + }, + onSuccess: async () => { + delayTypes(); } }); @@ -148,7 +181,10 @@ function baseConfig(adapter: string): tsup.Options { ], metafile: true, splitting: false, - treeshake: true + treeshake: true, + onSuccess: async () => { + delayTypes(); + } }; } diff --git a/app/package.json b/app/package.json index fb02b8e..4e188a1 100644 --- a/app/package.json +++ b/app/package.json @@ -5,14 +5,14 @@ "bin": "./dist/cli/index.js", "version": "0.3.4-alpha1", "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", "test": "ALL_TESTS=1 bun test --bail", "build": "NODE_ENV=production bun run build.ts --minify --types", "watch": "bun run build.ts --types --watch", "types": "bun tsc --noEmit", "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", "watch:css": "bun tailwindcss --watch -i src/ui/main.css -o ./dist/styles.css", "updater": "bun x npm-check-updates -ui", @@ -75,6 +75,7 @@ "tailwind-merge": "^2.5.4", "tailwindcss": "^3.4.14", "tailwindcss-animate": "^1.0.7", + "tsc-alias": "^1.8.10", "tsup": "^8.3.5", "vite": "^5.4.10", "vite-plugin-static-copy": "^2.0.0", @@ -90,75 +91,75 @@ }, "main": "./dist/index.js", "module": "./dist/index.js", - "types": "./dist/index.d.ts", + "types": "./dist/types/index.d.ts", "exports": { ".": { - "types": "./dist/index.d.ts", + "types": "./dist/types/index.d.ts", "import": "./dist/index.js", "require": "./dist/index.cjs" }, "./ui": { - "types": "./dist/ui/index.d.ts", + "types": "./dist/types/ui/index.d.ts", "import": "./dist/ui/index.js", "require": "./dist/ui/index.cjs" }, "./client": { - "types": "./dist/ui/client/index.d.ts", + "types": "./dist/types/ui/client/index.d.ts", "import": "./dist/ui/client/index.js", "require": "./dist/ui/client/index.cjs" }, "./data": { - "types": "./dist/data/index.d.ts", + "types": "./dist/types/data/index.d.ts", "import": "./dist/data/index.js", "require": "./dist/data/index.cjs" }, "./core": { - "types": "./dist/core/index.d.ts", + "types": "./dist/types/core/index.d.ts", "import": "./dist/core/index.js", "require": "./dist/core/index.cjs" }, "./utils": { - "types": "./dist/core/utils/index.d.ts", + "types": "./dist/types/core/utils/index.d.ts", "import": "./dist/core/utils/index.js", "require": "./dist/core/utils/index.cjs" }, "./cli": { - "types": "./dist/cli/index.d.ts", + "types": "./dist/types/cli/index.d.ts", "import": "./dist/cli/index.js", "require": "./dist/cli/index.cjs" }, "./adapter/cloudflare": { - "types": "./dist/adapter/cloudflare/index.d.ts", + "types": "./dist/types/adapter/cloudflare/index.d.ts", "import": "./dist/adapter/cloudflare/index.js", "require": "./dist/adapter/cloudflare/index.cjs" }, "./adapter/vite": { - "types": "./dist/adapter/vite/index.d.ts", + "types": "./dist/types/adapter/vite/index.d.ts", "import": "./dist/adapter/vite/index.js", "require": "./dist/adapter/vite/index.cjs" }, "./adapter/nextjs": { - "types": "./dist/adapter/nextjs/index.d.ts", + "types": "./dist/types/adapter/nextjs/index.d.ts", "import": "./dist/adapter/nextjs/index.js", "require": "./dist/adapter/nextjs/index.cjs" }, "./adapter/remix": { - "types": "./dist/adapter/remix/index.d.ts", + "types": "./dist/types/adapter/remix/index.d.ts", "import": "./dist/adapter/remix/index.js", "require": "./dist/adapter/remix/index.cjs" }, "./adapter/bun": { - "types": "./dist/adapter/bun/index.d.ts", + "types": "./dist/types/adapter/bun/index.d.ts", "import": "./dist/adapter/bun/index.js", "require": "./dist/adapter/bun/index.cjs" }, "./adapter/node": { - "types": "./dist/adapter/node/index.d.ts", + "types": "./dist/types/adapter/node/index.d.ts", "import": "./dist/adapter/node/index.js", "require": "./dist/adapter/node/index.cjs" }, "./adapter/astro": { - "types": "./dist/adapter/astro/index.d.ts", + "types": "./dist/types/adapter/astro/index.d.ts", "import": "./dist/adapter/astro/index.js", "require": "./dist/adapter/astro/index.cjs" }, diff --git a/app/src/Api.ts b/app/src/Api.ts index 5196622..d6c99fe 100644 --- a/app/src/Api.ts +++ b/app/src/Api.ts @@ -38,7 +38,7 @@ export class Api { private token_transport: "header" | "cookie" | "none" = "header"; public system!: SystemApi; - public data!: DataApi; + public data!: DataApi; public auth!: AuthApi; public media!: MediaApi; diff --git a/app/src/App.ts b/app/src/App.ts index d180a51..af68f58 100644 --- a/app/src/App.ts +++ b/app/src/App.ts @@ -37,6 +37,7 @@ export type AppConfig = InitialModuleConfigs; export class App { modules: ModuleManager; static readonly Events = AppEvents; + adminController?: AdminController; constructor( private connection: Connection, @@ -94,8 +95,12 @@ export class App { return this.modules.get(module).schema(); } + get server() { + return this.modules.server; + } + get fetch(): any { - return this.modules.server.fetch; + return this.server.fetch; } get module() { @@ -119,7 +124,8 @@ export class App { registerAdminController(config?: AdminControllerOptions) { // 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; } diff --git a/app/src/auth/AppAuth.ts b/app/src/auth/AppAuth.ts index ba7b00d..9a1c708 100644 --- a/app/src/auth/AppAuth.ts +++ b/app/src/auth/AppAuth.ts @@ -197,7 +197,7 @@ export class AppAuth extends Module { throw new Exception("User already exists"); } - const payload = { + const payload: any = { ...profile, strategy: strategy.getName(), strategy_value: identifier diff --git a/app/src/core/utils/test.ts b/app/src/core/utils/test.ts index cf33e1a..b06ac55 100644 --- a/app/src/core/utils/test.ts +++ b/app/src/core/utils/test.ts @@ -9,10 +9,25 @@ export async function withDisabledConsole( fn: () => Promise, severities: ConsoleSeverity[] = ["log"] ): Promise { - const enable = disableConsoleLog(severities); - const result = await fn(); - enable(); - return result; + const _oldConsoles = { + log: console.log, + warn: console.warn, + 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"]) { diff --git a/app/src/data/api/DataApi.ts b/app/src/data/api/DataApi.ts index 967a5f1..caf6a2a 100644 --- a/app/src/data/api/DataApi.ts +++ b/app/src/data/api/DataApi.ts @@ -5,7 +5,7 @@ export type DataApiOptions = BaseModuleApiOptions & { defaultQuery?: Partial; }; -export class DataApi extends ModuleApi { +export class DataApi extends ModuleApi { protected override getDefaultOptions(): Partial { return { basepath: "/api/data", @@ -15,48 +15,60 @@ export class DataApi extends ModuleApi { }; } - readOne( - entity: string, + readOne( + entity: E, id: PrimaryFieldType, query: Partial> = {} ) { - return this.get>([entity, id], query); + return this.get, "meta" | "data">>([entity as any, id], query); } - readMany(entity: string, query: Partial = {}) { - return this.get>( - [entity], - query ?? this.options.defaultQuery - ); - } - - readManyByReference( - entity: string, - id: PrimaryFieldType, - reference: string, + readMany( + entity: E, query: Partial = {} ) { - return this.get>( - [entity, id, reference], + return this.get, "meta" | "data">>( + [entity as any], query ?? this.options.defaultQuery ); } - createOne(entity: string, input: EntityData) { - return this.post>([entity], input); + readManyByReference< + 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 = {}) { + return this.get, "meta" | "data">>( + [entity as any, id, reference], + query ?? this.options.defaultQuery + ); } - updateOne(entity: string, id: PrimaryFieldType, input: EntityData) { - return this.patch>([entity, id], input); + createOne( + entity: E, + input: Data + ) { + return this.post>([entity as any], input); } - deleteOne(entity: string, id: PrimaryFieldType) { - return this.delete>([entity, id]); + updateOne( + entity: E, + id: PrimaryFieldType, + input: Partial + ) { + return this.patch>([entity as any, id], input); } - count(entity: string, where: RepoQuery["where"] = {}) { - return this.post>( - [entity, "fn", "count"], + deleteOne( + entity: E, + id: PrimaryFieldType + ) { + return this.delete>([entity as any, id]); + } + + count(entity: E, where: RepoQuery["where"] = {}) { + return this.post>( + [entity as any, "fn", "count"], where ); } diff --git a/app/src/data/api/DataController.ts b/app/src/data/api/DataController.ts index 3f459dc..86f3e42 100644 --- a/app/src/data/api/DataController.ts +++ b/app/src/data/api/DataController.ts @@ -165,13 +165,12 @@ export class DataController implements ClassController { // read entity schema .get("/schema.json", async (c) => { this.guard.throwUnlessGranted(DataPermissions.entityRead); - const url = new URL(c.req.url); - const $id = `${url.origin}${this.config.basepath}/schema.json`; + const $id = `${this.config.basepath}/schema.json`; const schemas = Object.fromEntries( this.em.entities.map((e) => [ 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 url = new URL(c.req.url); const base = `${url.origin}${this.config.basepath}`; - const $id = `${base}/schemas/${entity}`; + const $id = `${this.config.basepath}/schemas/${entity}`; return c.json({ $schema: `${base}/schema.json`, $id, diff --git a/app/src/data/entities/EntityManager.ts b/app/src/data/entities/EntityManager.ts index 674d7e2..d34c728 100644 --- a/app/src/data/entities/EntityManager.ts +++ b/app/src/data/entities/EntityManager.ts @@ -14,6 +14,14 @@ import { SchemaManager } from "../schema/SchemaManager"; import { Entity } from "./Entity"; import { type EntityData, Mutator, Repository } from "./index"; +type EntitySchema = E extends Entity + ? Name extends keyof DB + ? Name + : never + : E extends keyof DB + ? E + : never; + export class EntityManager { connection: Connection; @@ -87,10 +95,16 @@ export class EntityManager { this.entities.push(entity); } - entity(name: string): Entity { - const entity = this.entities.find((e) => e.name === name); + entity(e: Entity | string): Entity { + let entity: Entity | undefined; + if (typeof e === "string") { + entity = this.entities.find((entity) => entity.name === e); + } else { + entity = e; + } + if (!entity) { - throw new EntityNotDefinedException(name); + throw new EntityNotDefinedException(typeof e === "string" ? e : e.name); } return entity; @@ -162,28 +176,16 @@ export class EntityManager { return this.relations.relationReferencesOf(this.entity(entity_name)); } - repository(_entity: Entity | string) { - const entity = _entity instanceof Entity ? _entity : this.entity(_entity); - return new Repository(this, entity, this.emgr); + repository(entity: E): Repository> { + return this.repo(entity); } - repo( - _entity: E - ): Repository< - DB, - E extends Entity ? (Name extends keyof DB ? Name : never) : never - > { - return new Repository(this, _entity, this.emgr); + repo(entity: E): Repository> { + return new Repository(this, this.entity(entity), this.emgr); } - _repo(_entity: TB): Repository { - const entity = this.entity(_entity as any); - 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); + mutator(entity: E): Mutator> { + return new Mutator(this, this.entity(entity), this.emgr); } addIndex(index: EntityIndex, force = false) { diff --git a/app/src/data/entities/Mutator.ts b/app/src/data/entities/Mutator.ts index ed7f9ef..53b4cb2 100644 --- a/app/src/data/entities/Mutator.ts +++ b/app/src/data/entities/Mutator.ts @@ -25,7 +25,9 @@ export type MutatorResponse = { data: T; }; -export class Mutator implements EmitsEvents { +export class Mutator> + implements EmitsEvents +{ em: EntityManager; entity: Entity; static readonly Events = MutatorEvents; @@ -47,13 +49,13 @@ export class Mutator implements EmitsEvents { return this.em.connection.kysely; } - async getValidatedData(data: EntityData, context: TActionContext): Promise { + async getValidatedData(data: Given, context: TActionContext): Promise { const entity = this.entity; if (!context) { throw new Error("Context must be provided for validation"); } - const keys = Object.keys(data); + const keys = Object.keys(data as any); const validatedData: EntityData = {}; // get relational references/keys @@ -95,7 +97,7 @@ export class Mutator implements EmitsEvents { throw new Error(`No data left to update "${entity.name}"`); } - return validatedData; + return validatedData as Given; } protected async many(qb: MutatorQB): Promise { @@ -120,7 +122,7 @@ export class Mutator implements EmitsEvents { return { ...response, data: data[0]! }; } - async insertOne(data: EntityData): Promise> { + async insertOne(data: Data): Promise> { const entity = this.entity; if (entity.type === "system" && this.__unstable_disable_system_entity_creation) { throw new Error(`Creation of system entity "${entity.name}" is disabled`); @@ -154,10 +156,10 @@ export class Mutator implements EmitsEvents { 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> { + async updateOne(id: PrimaryFieldType, data: Data): Promise> { const entity = this.entity; if (!Number.isInteger(id)) { throw new Error("ID must be provided for update"); @@ -166,12 +168,16 @@ export class Mutator implements EmitsEvents { const validatedData = await this.getValidatedData(data, "update"); 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 .updateTable(entity.name) - .set(validatedData) + .set(validatedData as any) .where(entity.id().name, "=", id) .returning(entity.getSelect()); @@ -181,10 +187,10 @@ export class Mutator implements EmitsEvents { new Mutator.Events.MutatorUpdateAfter({ entity, entityId: id, data: res.data }) ); - return res; + return res as any; } - async deleteOne(id: PrimaryFieldType): Promise> { + async deleteOne(id: PrimaryFieldType): Promise> { const entity = this.entity; if (!Number.isInteger(id)) { throw new Error("ID must be provided for deletion"); @@ -203,7 +209,7 @@ export class Mutator implements EmitsEvents { new Mutator.Events.MutatorDeleteAfter({ entity, entityId: id, data: res.data }) ); - return res; + return res as any; } private getValidOptions(options?: Partial): Partial { @@ -250,47 +256,24 @@ export class Mutator implements EmitsEvents { } // @todo: decide whether entries should be deleted all at once or one by one (for events) - async deleteWhere(where?: RepoQuery["where"]): Promise> { + async deleteWhere(where?: RepoQuery["where"]): Promise> { const entity = this.entity; const qb = this.appendWhere(this.conn.deleteFrom(entity.name), where).returning( entity.getSelect() ); - //await this.emgr.emit(new Mutator.Events.MutatorDeleteBefore({ entity, entityId: id })); - - const res = await this.many(qb); - - /*await this.emgr.emit( - new Mutator.Events.MutatorDeleteAfter({ entity, entityId: id, data: res.data }) - );*/ - - return res; + return (await this.many(qb)) as any; } - async updateWhere( - data: EntityData, - where?: RepoQuery["where"] - ): Promise> { + async updateWhere(data: Data, where?: RepoQuery["where"]): Promise> { const entity = this.entity; - 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) - .set(validatedData) - //.where(entity.id().name, "=", id) + .set(validatedData as any) .returning(entity.getSelect()); - const res = await this.many(query); - - /*await this.emgr.emit( - new Mutator.Events.MutatorUpdateAfter({ entity, entityId: id, data: res.data }) - );*/ - - return res; + return (await this.many(query)) as any; } } diff --git a/app/src/data/entities/query/Repository.ts b/app/src/data/entities/query/Repository.ts index f5b576c..4391b32 100644 --- a/app/src/data/entities/query/Repository.ts +++ b/app/src/data/entities/query/Repository.ts @@ -272,7 +272,7 @@ export class Repository implements EmitsEve async findId( id: PrimaryFieldType, _options?: Partial> - ): Promise> { + ): Promise> { const { qb, options } = this.buildQuery( { ..._options, diff --git a/app/src/data/prototype/index.ts b/app/src/data/prototype/index.ts index d526c2e..8bdd07b 100644 --- a/app/src/data/prototype/index.ts +++ b/app/src/data/prototype/index.ts @@ -1,3 +1,5 @@ +import type { Generated } from "kysely"; +import { MediaField, type MediaFieldConfig, type MediaItem } from "media/MediaField"; import { BooleanField, type BooleanFieldConfig, @@ -25,15 +27,14 @@ import { type TEntityType, TextField, type TextFieldConfig -} from "data"; -import type { Generated } from "kysely"; -import { MediaField, type MediaFieldConfig, type MediaItem } from "media/MediaField"; +} from "../index"; type Options = { entity: { name: string; fields: Record> }; field_name: string; config: Config; is_required: boolean; + another?: string; }; const FieldMap = { @@ -239,7 +240,7 @@ export function relation(local: Local) { }; } -type InferEntityFields = T extends Entity +export type InferEntityFields = T extends Entity ? { [K in keyof Fields]: Fields[K] extends { _type: infer Type; _required: infer Required } ? Required extends true @@ -284,12 +285,25 @@ type OptionalUndefined< } >; -type InferField = Field extends { _type: infer Type; _required: infer Required } +export type InferField = Field extends { _type: infer Type; _required: infer Required } ? Required extends true ? Type : Type | undefined : never; +const n = number(); +type T2 = InferField; + +const users = entity("users", { + name: text(), + email: text(), + created_at: datetime(), + updated_at: datetime() +}); +type TUsersFields = InferEntityFields; +type TUsers = Schema; +type TUsers2 = Simplify>>; + export type InsertSchema = Simplify>>; -export type Schema = { id: Generated } & InsertSchema; +export type Schema = Simplify<{ id: Generated } & InsertSchema>; export type FieldSchema = Simplify>>; diff --git a/app/src/index.ts b/app/src/index.ts index 578ab5d..67d54c8 100644 --- a/app/src/index.ts +++ b/app/src/index.ts @@ -4,8 +4,10 @@ export { getDefaultConfig, getDefaultSchema, type ModuleConfigs, - type ModuleSchemas -} from "modules/ModuleManager"; + type ModuleSchemas, + type ModuleManagerOptions, + type ModuleBuildContext +} from "./modules/ModuleManager"; export type * from "./adapter"; export { Api, type ApiOptions } from "./Api"; diff --git a/app/src/media/api/MediaController.ts b/app/src/media/api/MediaController.ts index 9597759..2a1a304 100644 --- a/app/src/media/api/MediaController.ts +++ b/app/src/media/api/MediaController.ts @@ -174,7 +174,7 @@ export class MediaController implements ClassController { const result = await mutator.insertOne({ ...this.media.uploadedEventDataToMediaPayload(info), ...mediaRef - }); + } as any); mutator.__unstable_toggleSystemEntityCreation(true); // delete items if needed diff --git a/app/src/modules/Module.ts b/app/src/modules/Module.ts index ecdf4ce..704e420 100644 --- a/app/src/modules/Module.ts +++ b/app/src/modules/Module.ts @@ -5,10 +5,10 @@ import type { Static, TSchema } from "core/utils"; import type { Connection, EntityManager } from "data"; import type { Hono } from "hono"; -export type ModuleBuildContext = { +export type ModuleBuildContext = { connection: Connection; server: Hono; - em: EntityManager; + em: EntityManager; emgr: EventManager; guard: Guard; }; diff --git a/app/src/modules/ModuleManager.ts b/app/src/modules/ModuleManager.ts index db7b285..bed6c18 100644 --- a/app/src/modules/ModuleManager.ts +++ b/app/src/modules/ModuleManager.ts @@ -35,6 +35,8 @@ import { AppFlows } from "../flows/AppFlows"; import { AppMedia } from "../media/AppMedia"; import type { Module, ModuleBuildContext } from "./Module"; +export type { ModuleBuildContext }; + export const MODULES = { server: AppServer, data: AppData, @@ -75,7 +77,10 @@ export type ModuleManagerOptions = { ) => Promise; // base path for the hono instance basePath?: string; + // doesn't perform validity checks for given/fetched config trustFetched?: boolean; + // runs when initial config provided on a fresh database + seed?: (ctx: ModuleBuildContext) => Promise; }; type ConfigTable = { @@ -294,7 +299,7 @@ export class ModuleManager { version, json: configs, updated_at: new Date() - }, + } as any, { type: "config", version @@ -448,6 +453,9 @@ export class ModuleManager { await this.buildModules(); await this.save(); + // run initial setup + await this.setupInitial(); + this.logger.clear(); return this; } @@ -462,6 +470,18 @@ export class ModuleManager { 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(key: K): Modules[K] { if (!(key in this.modules)) { throw new Error(`Module "${key}" doesn't exist, cannot get`); diff --git a/app/src/ui/client/api/use-api.ts b/app/src/ui/client/api/use-api.ts index 6d75f82..eab68d4 100644 --- a/app/src/ui/client/api/use-api.ts +++ b/app/src/ui/client/api/use-api.ts @@ -5,14 +5,14 @@ import { useApi } from "ui/client"; export const useApiQuery = < Data, - RefineFn extends (data: ResponseObject) => any = (data: ResponseObject) => Data + RefineFn extends (data: ResponseObject) => unknown = (data: ResponseObject) => Data >( fn: (api: Api) => FetchPromise, options?: SWRConfiguration & { enabled?: boolean; refine?: RefineFn } ) => { const api = useApi(); const promise = fn(api); - const refine = options?.refine ?? ((data: ResponseObject) => data); + const refine = options?.refine ?? ((data: any) => data); const fetcher = () => promise.execute().then(refine); const key = promise.key(); diff --git a/app/src/ui/client/api/use-entity.ts b/app/src/ui/client/api/use-entity.ts index 23be395..3c57bad 100644 --- a/app/src/ui/client/api/use-entity.ts +++ b/app/src/ui/client/api/use-entity.ts @@ -1,8 +1,8 @@ import type { PrimaryFieldType } from "core"; import { objectTransform } from "core/utils"; import type { EntityData, RepoQuery } from "data"; -import type { ResponseObject } from "modules/ModuleApi"; -import useSWR, { type SWRConfiguration } from "swr"; +import type { ModuleApi, ResponseObject } from "modules/ModuleApi"; +import useSWR, { type SWRConfiguration, useSWRConfig } from "swr"; import { useApi } from "ui/client"; export class UseEntityApiError extends Error { @@ -15,9 +15,19 @@ export class UseEntityApiError extends Error { } } +function Test() { + const { read } = useEntity("users"); + async () => { + const data = await read(); + }; + + return null; +} + export const useEntity = < - Entity extends string, - Id extends PrimaryFieldType | undefined = undefined + Entity extends keyof DB | string, + Id extends PrimaryFieldType | undefined = undefined, + Data = Entity extends keyof DB ? DB[Entity] : EntityData >( entity: Entity, id?: Id @@ -25,7 +35,7 @@ export const useEntity = < const api = useApi().data; return { - create: async (input: EntityData) => { + create: async (input: Omit) => { const res = await api.createOne(entity, input); if (!res.ok) { throw new UseEntityApiError(res.data, res.res, "Failed to create entity"); @@ -37,9 +47,12 @@ export const useEntity = < if (!res.ok) { throw new UseEntityApiError(res.data, res.res, "Failed to read entity"); } - return res; + // must be manually typed + return res as unknown as Id extends undefined + ? ResponseObject + : ResponseObject; }, - update: async (input: Partial, _id: PrimaryFieldType | undefined = id) => { + update: async (input: Partial>, _id: PrimaryFieldType | undefined = id) => { if (!_id) { throw new Error("id is required"); } @@ -63,8 +76,17 @@ export const useEntity = < }; }; +export function makeKey(api: ModuleApi, entity: string, id?: PrimaryFieldType) { + return ( + "/" + + [...(api.options?.basepath?.split("/") ?? []), entity, ...(id ? [id] : [])] + .filter(Boolean) + .join("/") + ); +} + export const useEntityQuery = < - Entity extends string, + Entity extends keyof DB | string, Id extends PrimaryFieldType | undefined = undefined >( entity: Entity, @@ -72,28 +94,28 @@ export const useEntityQuery = < query?: Partial, options?: SWRConfiguration & { enabled?: boolean } ) => { + const { mutate } = useSWRConfig(); const api = useApi().data; - const key = - options?.enabled !== false - ? [...(api.options?.basepath?.split("/") ?? []), entity, ...(id ? [id] : [])].filter( - Boolean - ) - : null; - const { read, ...actions } = useEntity(entity, id) as any; + const key = makeKey(api, entity, id); + const { read, ...actions } = useEntity(entity, id); const fetcher = () => read(query); - type T = Awaited>; - const swr = useSWR(key, fetcher, { + type T = Awaited>; + const swr = useSWR(options?.enabled === false ? null : key, fetcher as any, { revalidateOnFocus: false, keepPreviousData: false, ...options }); const mapped = objectTransform(actions, (action) => { - if (action === "read") return; + return async (...args: any) => { + // @ts-ignore + const res = await action(...args); - return async (...args) => { - return swr.mutate(action(...args)) as any; + // mutate the key + list key + mutate(key); + if (id) mutate(makeKey(api, entity)); + return res; }; }) as Omit>, "read">; @@ -106,14 +128,14 @@ export const useEntityQuery = < }; export const useEntityMutate = < - Entity extends string, + Entity extends keyof DB | string, Id extends PrimaryFieldType | undefined = undefined >( entity: Entity, id?: Id, options?: SWRConfiguration ) => { - const { data, ...$q } = useEntityQuery(entity, id, undefined, { + const { data, ...$q } = useEntityQuery(entity, id, undefined, { ...options, enabled: false }); diff --git a/app/src/ui/components/code/CodeEditor.tsx b/app/src/ui/components/code/CodeEditor.tsx index 8dcca60..55d119b 100644 --- a/app/src/ui/components/code/CodeEditor.tsx +++ b/app/src/ui/components/code/CodeEditor.tsx @@ -1,7 +1,6 @@ -import type { ReactCodeMirrorProps } from "@uiw/react-codemirror"; -import { Suspense, lazy } from "react"; +import { default as CodeMirror, type ReactCodeMirrorProps } from "@uiw/react-codemirror"; + import { useBknd } from "ui/client/bknd"; -const CodeMirror = lazy(() => import("@uiw/react-codemirror")); export default function CodeEditor({ editable, basicSetup, ...props }: ReactCodeMirrorProps) { const b = useBknd(); @@ -15,13 +14,11 @@ export default function CodeEditor({ editable, basicSetup, ...props }: ReactCode : basicSetup; return ( - - - + ); } diff --git a/app/src/ui/components/form/json-schema/JsonSchemaForm.tsx b/app/src/ui/components/form/json-schema/JsonSchemaForm.tsx index 73330d3..d722dde 100644 --- a/app/src/ui/components/form/json-schema/JsonSchemaForm.tsx +++ b/app/src/ui/components/form/json-schema/JsonSchemaForm.tsx @@ -1,15 +1,12 @@ import type { Schema } from "@cfworker/json-schema"; import Form from "@rjsf/core"; import type { RJSFSchema, UiSchema } from "@rjsf/utils"; +import { cloneDeep } from "lodash-es"; import { forwardRef, useId, useImperativeHandle, useRef, useState } from "react"; -//import { JsonSchemaValidator } from "./JsonSchemaValidator"; import { fields as Fields } from "./fields"; import { templates as Templates } from "./templates"; -import { widgets as Widgets } from "./widgets"; -import "./styles.css"; -import { filterKeys } from "core/utils"; -import { cloneDeep } from "lodash-es"; import { RJSFTypeboxValidator } from "./typebox/RJSFTypeboxValidator"; +import { widgets as Widgets } from "./widgets"; const validator = new RJSFTypeboxValidator(); diff --git a/app/src/ui/components/form/json-schema/index.tsx b/app/src/ui/components/form/json-schema/index.tsx new file mode 100644 index 0000000..4af8a4e --- /dev/null +++ b/app/src/ui/components/form/json-schema/index.tsx @@ -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((props, ref) => { + return ( + + + + ); +}); diff --git a/app/src/ui/modals/debug/SchemaFormModal.tsx b/app/src/ui/modals/debug/SchemaFormModal.tsx index 0bfab66..72c1c89 100644 --- a/app/src/ui/modals/debug/SchemaFormModal.tsx +++ b/app/src/ui/modals/debug/SchemaFormModal.tsx @@ -4,7 +4,7 @@ import { JsonSchemaForm, type JsonSchemaFormProps, type JsonSchemaFormRef -} from "ui/components/form/json-schema/JsonSchemaForm"; +} from "ui/components/form/json-schema"; import type { ContextModalProps } from "@mantine/modals"; diff --git a/app/src/ui/modules/data/components/fields/EntityJsonSchemaFormField.tsx b/app/src/ui/modules/data/components/fields/EntityJsonSchemaFormField.tsx index 7b830d2..82a55a9 100644 --- a/app/src/ui/modules/data/components/fields/EntityJsonSchemaFormField.tsx +++ b/app/src/ui/modules/data/components/fields/EntityJsonSchemaFormField.tsx @@ -1,14 +1,8 @@ import type { FieldApi } from "@tanstack/react-form"; import type { EntityData, JsonSchemaField } from "data"; -import { Suspense, lazy } from "react"; import * as Formy from "ui/components/form/Formy"; import { FieldLabel } from "ui/components/form/Formy"; - -const JsonSchemaForm = lazy(() => - import("ui/components/form/json-schema/JsonSchemaForm").then((m) => ({ - default: m.JsonSchemaForm - })) -); +import { JsonSchemaForm } from "ui/components/form/json-schema"; export function EntityJsonSchemaFormField({ fieldApi, @@ -34,23 +28,21 @@ export function EntityJsonSchemaFormField({ return ( - Loading...}> -
- -
-
+
+ +
); } diff --git a/app/src/ui/modules/flows/components/TriggerComponent.tsx b/app/src/ui/modules/flows/components/TriggerComponent.tsx index 7497234..87e906b 100644 --- a/app/src/ui/modules/flows/components/TriggerComponent.tsx +++ b/app/src/ui/modules/flows/components/TriggerComponent.tsx @@ -1,14 +1,9 @@ import { Handle, type Node, type NodeProps, Position } from "@xyflow/react"; import { Const, Type, transformObject } from "core/utils"; -import { type TaskRenderProps, type Trigger, TriggerMap } from "flows"; -import { Suspense, lazy } from "react"; +import { type Trigger, TriggerMap } from "flows"; import type { IconType } from "react-icons"; import { TbCircleLetterT } from "react-icons/tb"; -const JsonSchemaForm = lazy(() => - import("ui/components/form/json-schema/JsonSchemaForm").then((m) => ({ - default: m.JsonSchemaForm - })) -); +import { JsonSchemaForm } from "ui/components/form/json-schema"; export type TaskComponentProps = NodeProps> & { Icon?: IconType; @@ -48,17 +43,15 @@ export function TriggerComponent({
- Loading...
}> - - +
- import("ui/components/form/json-schema/JsonSchemaForm").then((m) => ({ - default: m.JsonSchemaForm - })) -); +import { JsonSchemaForm } from "ui/components/form/json-schema"; export type TaskFormProps = { task: Task; @@ -26,16 +19,14 @@ export function TaskForm({ task, onChange, ...props }: TaskFormProps) { //console.log("uiSchema", uiSchema); return ( - Loading...}> - - + ); } diff --git a/app/src/ui/routes/auth/auth.settings.tsx b/app/src/ui/routes/auth/auth.settings.tsx index 2716d3c..5b19651 100644 --- a/app/src/ui/routes/auth/auth.settings.tsx +++ b/app/src/ui/routes/auth/auth.settings.tsx @@ -5,10 +5,7 @@ import { useBkndAuth } from "ui/client/schema/auth/use-bknd-auth"; import { useBkndData } from "ui/client/schema/data/use-bknd-data"; import { Button } from "ui/components/buttons/Button"; import { Alert } from "ui/components/display/Alert"; -import { - JsonSchemaForm, - type JsonSchemaFormRef -} from "ui/components/form/json-schema/JsonSchemaForm"; +import { JsonSchemaForm, type JsonSchemaFormRef } from "ui/components/form/json-schema"; import * as AppShell from "ui/layouts/AppShell/AppShell"; import { useNavigate } from "ui/lib/routes"; import { extractSchema } from "../settings/utils/schema"; diff --git a/app/src/ui/routes/auth/auth.strategies.tsx b/app/src/ui/routes/auth/auth.strategies.tsx index ebf18c0..2792767 100644 --- a/app/src/ui/routes/auth/auth.strategies.tsx +++ b/app/src/ui/routes/auth/auth.strategies.tsx @@ -1,9 +1,7 @@ import { cloneDeep, omit } from "lodash-es"; import { useBknd } from "ui/client/bknd"; import { Button } from "ui/components/buttons/Button"; -import { JsonSchemaForm } from "ui/components/form/json-schema/JsonSchemaForm"; import * as AppShell from "../../layouts/AppShell/AppShell"; -import { extractSchema } from "../settings/utils/schema"; export function AuthStrategiesList() { useBknd({ withSecrets: true }); diff --git a/app/src/ui/routes/data/data.schema.$entity.tsx b/app/src/ui/routes/data/data.schema.$entity.tsx index 838851c..5d797c7 100644 --- a/app/src/ui/routes/data/data.schema.$entity.tsx +++ b/app/src/ui/routes/data/data.schema.$entity.tsx @@ -13,10 +13,7 @@ import { useBkndData } from "ui/client/schema/data/use-bknd-data"; import { Button } from "ui/components/buttons/Button"; import { IconButton } from "ui/components/buttons/IconButton"; import { Empty } from "ui/components/display/Empty"; -import { - JsonSchemaForm, - type JsonSchemaFormRef -} from "ui/components/form/json-schema/JsonSchemaForm"; +import { JsonSchemaForm, type JsonSchemaFormRef } from "ui/components/form/json-schema"; import { Dropdown } from "ui/components/overlay/Dropdown"; import * as AppShell from "ui/layouts/AppShell/AppShell"; import { Breadcrumbs2 } from "ui/layouts/AppShell/Breadcrumbs2"; diff --git a/app/src/ui/routes/data/forms/entity.fields.form.tsx b/app/src/ui/routes/data/forms/entity.fields.form.tsx index 3f70848..bcc315d 100644 --- a/app/src/ui/routes/data/forms/entity.fields.form.tsx +++ b/app/src/ui/routes/data/forms/entity.fields.form.tsx @@ -22,7 +22,7 @@ import { Button } from "ui/components/buttons/Button"; import { IconButton } from "ui/components/buttons/IconButton"; import { JsonViewer } from "ui/components/code/JsonViewer"; import { MantineSwitch } from "ui/components/form/hook-form-mantine/MantineSwitch"; -import { JsonSchemaForm } from "ui/components/form/json-schema/JsonSchemaForm"; +import { JsonSchemaForm } from "ui/components/form/json-schema"; import { type SortableItemProps, SortableList } from "ui/components/list/SortableList"; import { Popover } from "ui/components/overlay/Popover"; import { fieldSpecs } from "ui/modules/data/components/fields-specs"; diff --git a/app/src/ui/routes/index.tsx b/app/src/ui/routes/index.tsx index a2c3771..aedfc58 100644 --- a/app/src/ui/routes/index.tsx +++ b/app/src/ui/routes/index.tsx @@ -1,14 +1,19 @@ import { Suspense, lazy } from "react"; import { useBknd } from "ui/client/bknd"; import { Route, Router, Switch } from "wouter"; +import AuthRoutes from "./auth"; import { AuthLogin } from "./auth/auth.login"; +import DataRoutes from "./data"; +import FlowRoutes from "./flows"; +import MediaRoutes from "./media"; import { Root, RootEmpty } from "./root"; +import SettingsRoutes from "./settings"; -const DataRoutes = lazy(() => import("./data")); +/*const DataRoutes = lazy(() => import("./data")); const AuthRoutes = lazy(() => import("./auth")); const MediaRoutes = lazy(() => import("./media")); const FlowRoutes = lazy(() => import("./flows")); -const SettingsRoutes = lazy(() => import("./settings")); +const SettingsRoutes = lazy(() => import("./settings"));*/ // @ts-ignore const TestRoutes = lazy(() => import("./test")); diff --git a/app/src/ui/routes/settings/components/Setting.tsx b/app/src/ui/routes/settings/components/Setting.tsx index 9c02c10..20a852b 100644 --- a/app/src/ui/routes/settings/components/Setting.tsx +++ b/app/src/ui/routes/settings/components/Setting.tsx @@ -8,10 +8,7 @@ import { Button } from "ui/components/buttons/Button"; import { IconButton } from "ui/components/buttons/IconButton"; import { Alert } from "ui/components/display/Alert"; import { Empty } from "ui/components/display/Empty"; -import { - JsonSchemaForm, - type JsonSchemaFormRef -} from "ui/components/form/json-schema/JsonSchemaForm"; +import { JsonSchemaForm, type JsonSchemaFormRef } from "ui/components/form/json-schema"; import { Dropdown } from "ui/components/overlay/Dropdown"; import { DataTable } from "ui/components/table/DataTable"; import { useEvent } from "ui/hooks/use-event"; diff --git a/app/src/ui/routes/settings/components/SettingNewModal.tsx b/app/src/ui/routes/settings/components/SettingNewModal.tsx index a8bc93c..5268f53 100644 --- a/app/src/ui/routes/settings/components/SettingNewModal.tsx +++ b/app/src/ui/routes/settings/components/SettingNewModal.tsx @@ -3,16 +3,13 @@ import type { TObject } from "core/utils"; import { omit } from "lodash-es"; import { useRef, useState } from "react"; import { TbCirclePlus, TbVariable } from "react-icons/tb"; +import { useBknd } from "ui/client/BkndProvider"; +import { Button } from "ui/components/buttons/Button"; +import * as Formy from "ui/components/form/Formy"; +import { JsonSchemaForm, type JsonSchemaFormRef } from "ui/components/form/json-schema"; +import { Dropdown } from "ui/components/overlay/Dropdown"; +import { Modal } from "ui/components/overlay/Modal"; import { useLocation } from "wouter"; -import { useBknd } from "../../../client/BkndProvider"; -import { Button } from "../../../components/buttons/Button"; -import * as Formy from "../../../components/form/Formy"; -import { - JsonSchemaForm, - type JsonSchemaFormRef -} from "../../../components/form/json-schema/JsonSchemaForm"; -import { Dropdown } from "../../../components/overlay/Dropdown"; -import { Modal } from "../../../components/overlay/Modal"; export type SettingsNewModalProps = { schema: TObject; diff --git a/app/src/ui/routes/test/tests/flow-create-schema-test.tsx b/app/src/ui/routes/test/tests/flow-create-schema-test.tsx index e27d170..2301240 100644 --- a/app/src/ui/routes/test/tests/flow-create-schema-test.tsx +++ b/app/src/ui/routes/test/tests/flow-create-schema-test.tsx @@ -2,7 +2,7 @@ import { parse } from "core/utils"; import { AppFlows } from "flows/AppFlows"; import { useState } from "react"; import { JsonViewer } from "../../../components/code/JsonViewer"; -import { JsonSchemaForm } from "../../../components/form/json-schema/JsonSchemaForm"; +import { JsonSchemaForm } from "../../../components/form/json-schema"; import { Scrollable } from "../../../layouts/AppShell/AppShell"; export default function FlowCreateSchemaTest() { diff --git a/app/src/ui/routes/test/tests/jsonform-test/index.tsx b/app/src/ui/routes/test/tests/jsonform-test/index.tsx index 2909887..dd6ec4c 100644 --- a/app/src/ui/routes/test/tests/jsonform-test/index.tsx +++ b/app/src/ui/routes/test/tests/jsonform-test/index.tsx @@ -2,12 +2,9 @@ import Form from "@rjsf/core"; import type { RJSFSchema, UiSchema } from "@rjsf/utils"; import { useRef } from "react"; import { TbPlus, TbTrash } from "react-icons/tb"; -import { Button } from "../../../../components/buttons/Button"; +import { Button } from "ui/components/buttons/Button"; +import { JsonSchemaForm, type JsonSchemaFormRef } from "ui/components/form/json-schema"; import * as Formy from "../../../../components/form/Formy"; -import { - JsonSchemaForm, - type JsonSchemaFormRef -} from "../../../../components/form/json-schema/JsonSchemaForm"; import * as AppShell from "../../../../layouts/AppShell/AppShell"; class CfJsonSchemaValidator {} diff --git a/app/src/ui/routes/test/tests/query-jsonform.tsx b/app/src/ui/routes/test/tests/query-jsonform.tsx index 204785d..41b63a6 100644 --- a/app/src/ui/routes/test/tests/query-jsonform.tsx +++ b/app/src/ui/routes/test/tests/query-jsonform.tsx @@ -1,7 +1,7 @@ import type { Schema } from "@cfworker/json-schema"; import { useState } from "react"; -import { JsonSchemaForm } from "../../../components/form/json-schema/JsonSchemaForm"; -import { Scrollable } from "../../../layouts/AppShell/AppShell"; +import { JsonSchemaForm } from "ui/components/form/json-schema"; +import { Scrollable } from "ui/layouts/AppShell/AppShell"; const schema: Schema = { definitions: { @@ -9,52 +9,52 @@ const schema: Schema = { anyOf: [ { title: "String", - type: "string", + type: "string" }, { title: "Number", - type: "number", + type: "number" }, { title: "Boolean", - type: "boolean", - }, - ], + type: "boolean" + } + ] }, numeric: { anyOf: [ { title: "Number", - type: "number", + type: "number" }, { title: "Datetime", type: "string", - format: "date-time", + format: "date-time" }, { title: "Date", type: "string", - format: "date", + format: "date" }, { title: "Time", type: "string", - format: "time", - }, - ], + format: "time" + } + ] }, boolean: { title: "Boolean", - type: "boolean", - }, + type: "boolean" + } }, type: "object", properties: { operand: { enum: ["$and", "$or"], default: "$and", - type: "string", + type: "string" }, conditions: { type: "array", @@ -64,10 +64,10 @@ const schema: Schema = { operand: { enum: ["$and", "$or"], default: "$and", - type: "string", + type: "string" }, key: { - type: "string", + type: "string" }, operator: { type: "array", @@ -78,30 +78,30 @@ const schema: Schema = { type: "object", properties: { $eq: { - $ref: "#/definitions/primitive", - }, + $ref: "#/definitions/primitive" + } }, - required: ["$eq"], + required: ["$eq"] }, { title: "Lower than", type: "object", properties: { $lt: { - $ref: "#/definitions/numeric", - }, + $ref: "#/definitions/numeric" + } }, - required: ["$lt"], + required: ["$lt"] }, { title: "Greather than", type: "object", properties: { $gt: { - $ref: "#/definitions/numeric", - }, + $ref: "#/definitions/numeric" + } }, - required: ["$gt"], + required: ["$gt"] }, { title: "Between", @@ -110,13 +110,13 @@ const schema: Schema = { $between: { type: "array", items: { - $ref: "#/definitions/numeric", + $ref: "#/definitions/numeric" }, minItems: 2, - maxItems: 2, - }, + maxItems: 2 + } }, - required: ["$between"], + required: ["$between"] }, { title: "In", @@ -125,23 +125,23 @@ const schema: Schema = { $in: { type: "array", items: { - $ref: "#/definitions/primitive", + $ref: "#/definitions/primitive" }, - minItems: 1, - }, - }, - }, - ], + minItems: 1 + } + } + } + ] }, - minItems: 1, - }, + minItems: 1 + } }, - required: ["key", "operator"], + required: ["key", "operator"] }, - minItems: 1, - }, + minItems: 1 + } }, - required: ["operand", "conditions"], + required: ["operand", "conditions"] }; export default function QueryJsonFormTest() { diff --git a/app/src/ui/routes/test/tests/schema-test.tsx b/app/src/ui/routes/test/tests/schema-test.tsx index 8865ed6..496fe70 100644 --- a/app/src/ui/routes/test/tests/schema-test.tsx +++ b/app/src/ui/routes/test/tests/schema-test.tsx @@ -1,8 +1,8 @@ import { useEffect, useState } from "react"; import { twMerge } from "tailwind-merge"; -import { useBknd } from "../../../client/BkndProvider"; -import { JsonSchemaForm } from "../../../components/form/json-schema/JsonSchemaForm"; -import { Scrollable } from "../../../layouts/AppShell/AppShell"; +import { useBknd } from "ui/client/BkndProvider"; +import { JsonSchemaForm } from "ui/components/form/json-schema"; +import { Scrollable } from "ui/layouts/AppShell/AppShell"; function useSchema() { const [schema, setSchema] = useState(); diff --git a/app/src/ui/routes/test/tests/swr-and-api.tsx b/app/src/ui/routes/test/tests/swr-and-api.tsx index 53c632e..4d4d88f 100644 --- a/app/src/ui/routes/test/tests/swr-and-api.tsx +++ b/app/src/ui/routes/test/tests/swr-and-api.tsx @@ -1,7 +1,20 @@ import { useEffect, useState } from "react"; -import { useApiQuery } from "ui/client"; +import { useApi, useApiQuery } from "ui/client"; import { Scrollable } from "ui/layouts/AppShell/AppShell"; +function Bla() { + const api = useApi(); + + useEffect(() => { + (async () => { + const one = await api.data.readOne("users", 1); + const many = await api.data.readMany("users"); + })(); + }, []); + + return null; +} + export default function SWRAndAPI() { const [text, setText] = useState(""); const { data, ...r } = useApiQuery((api) => api.data.readOne("comments", 1), { @@ -16,7 +29,7 @@ export default function SWRAndAPI() { return ( -
{JSON.stringify(r.promise.keyArray({ search: false }))}
+
{JSON.stringify(r.key)}
{r.error &&
failed to load
} {r.isLoading &&
loading...
} {data &&
{JSON.stringify(data, null, 2)}
} diff --git a/app/src/ui/routes/test/tests/swr-and-data-api.tsx b/app/src/ui/routes/test/tests/swr-and-data-api.tsx index 7c2e2a6..ab82e24 100644 --- a/app/src/ui/routes/test/tests/swr-and-data-api.tsx +++ b/app/src/ui/routes/test/tests/swr-and-data-api.tsx @@ -13,7 +13,7 @@ export default function SwrAndDataApi() { function QueryDataApi() { const [text, setText] = useState(""); - const { data, update, ...r } = useEntityQuery("comments", 1, {}); + const { data, update, ...r } = useEntityQuery("comments", 2); const comment = data ? data : null; useEffect(() => { @@ -45,10 +45,10 @@ function QueryDataApi() { function DirectDataApi() { const [data, setData] = useState(); - const { create, read, update, _delete } = useEntity("comments", 1); + const { create, read, update, _delete } = useEntity("users"); useEffect(() => { - read().then(setData); + read().then((data) => setData(data)); }, []); return
{JSON.stringify(data, null, 2)}
; diff --git a/app/tsconfig.json b/app/tsconfig.json index 764dacd..abc39f6 100644 --- a/app/tsconfig.json +++ b/app/tsconfig.json @@ -26,14 +26,13 @@ "esModuleInterop": true, "skipLibCheck": true, "rootDir": "./src", - "outDir": "./dist", - "tsBuildInfoFile": "./dist/tsconfig.tsbuildinfo", + "outDir": "./dist/types", "baseUrl": ".", "paths": { "*": ["./src/*"], "bknd": ["./src/*"] } }, - "include": ["./src/**/*.ts", "./src/**/*.tsx", "./env.d.ts"], - "exclude": ["node_modules", "dist/**/*", "../examples/bun"] + "include": ["./src/**/*.ts", "./src/**/*.tsx"], + "exclude": ["node_modules", "dist", "dist/types", "**/*.d.ts"] } \ No newline at end of file diff --git a/bun.lockb b/bun.lockb index 26c6843..7b73841 100755 Binary files a/bun.lockb and b/bun.lockb differ