diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 13f989af..a807d563 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -20,7 +20,7 @@ jobs: - name: Setup Bun uses: oven-sh/setup-bun@v1 with: - bun-version: "1.2.14" + bun-version: "1.2.19" - name: Install dependencies working-directory: ./app diff --git a/app/__test__/_assets/.gitignore b/app/__test__/_assets/.gitignore index e540f5b3..22bd3dc3 100644 --- a/app/__test__/_assets/.gitignore +++ b/app/__test__/_assets/.gitignore @@ -1 +1,2 @@ -tmp/* \ No newline at end of file +tmp/* +!tmp/.gitkeep \ No newline at end of file diff --git a/app/__test__/_assets/tmp/.gitkeep b/app/__test__/_assets/tmp/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/app/__test__/api/DataApi.spec.ts b/app/__test__/api/DataApi.spec.ts index cb48d7c3..b0e19fd2 100644 --- a/app/__test__/api/DataApi.spec.ts +++ b/app/__test__/api/DataApi.spec.ts @@ -1,12 +1,12 @@ import { afterAll, beforeAll, describe, expect, it } from "bun:test"; -import { Guard } from "../../src/auth"; +import { Guard } from "../../src/auth/authorize/Guard"; import { DataApi } from "../../src/data/api/DataApi"; import { DataController } from "../../src/data/api/DataController"; import { dataConfigSchema } from "../../src/data/data-schema"; import * as proto from "../../src/data/prototype"; import { schemaToEm } from "../helper"; import { disableConsoleLog, enableConsoleLog } from "core/utils/test"; -import { parse } from "core/object/schema"; +import { parse } from "core/utils/schema"; beforeAll(disableConsoleLog); afterAll(enableConsoleLog); @@ -202,7 +202,7 @@ describe("DataApi", () => { { // create many const res = await api.createMany("posts", payload); - expect(res.data.length).toEqual(4); + expect(res.data?.length).toEqual(4); expect(res.ok).toBeTrue(); } diff --git a/app/__test__/app/AppServer.spec.ts b/app/__test__/app/AppServer.spec.ts index 87354d00..40ea4144 100644 --- a/app/__test__/app/AppServer.spec.ts +++ b/app/__test__/app/AppServer.spec.ts @@ -8,6 +8,7 @@ describe("AppServer", () => { expect(server).toBeDefined(); expect(server.config).toEqual({ cors: { + allow_credentials: true, origin: "*", allow_methods: ["GET", "POST", "PATCH", "PUT", "DELETE"], allow_headers: ["Content-Type", "Content-Length", "Authorization", "Accept"], @@ -25,6 +26,7 @@ describe("AppServer", () => { expect(server).toBeDefined(); expect(server.config).toEqual({ cors: { + allow_credentials: true, origin: "https", allow_methods: ["GET", "POST"], allow_headers: ["Content-Type", "Content-Length", "Authorization", "Accept"], diff --git a/app/__test__/auth/Authenticator.spec.ts b/app/__test__/auth/Authenticator.spec.ts index b415cd0a..0794528e 100644 --- a/app/__test__/auth/Authenticator.spec.ts +++ b/app/__test__/auth/Authenticator.spec.ts @@ -1,45 +1,3 @@ import { describe, expect, test } from "bun:test"; -import { Authenticator, type User, type UserPool } from "../../src/auth"; -import { cookieConfig } from "../../src/auth/authenticate/Authenticator"; -import { PasswordStrategy } from "../../src/auth/authenticate/strategies/PasswordStrategy"; -import { parse } from "core/object/schema"; -/*class MemoryUserPool implements UserPool { - constructor(private users: User[] = []) {} - - async findBy(prop: "id" | "email" | "username", value: string | number) { - return this.users.find((user) => user[prop] === value); - } - - async create(user: Pick) { - const id = this.users.length + 1; - const newUser = { ...user, id, username: user.email }; - this.users.push(newUser); - return newUser; - } -}*/ - -describe("Authenticator", async () => { - test("cookie options", async () => { - console.log("parsed", parse(cookieConfig, undefined)); - console.log(cookieConfig.template({})); - }); - /*const userpool = new MemoryUserPool([ - { id: 1, email: "d", username: "test", password: await hash.sha256("test") }, - ]); - - test("sha256 login", async () => { - const auth = new Authenticator(userpool, { - password: new PasswordStrategy({ - hashing: "sha256", - }), - }); - - const { token } = await auth.login("password", { email: "d", password: "test" }); - expect(token).toBeDefined(); - - const { iat, ...decoded } = decodeJwt(token); - expect(decoded).toEqual({ id: 1, email: "d", username: "test" }); - expect(await auth.verify(token)).toBe(true); - });*/ -}); +describe("Authenticator", async () => {}); diff --git a/app/__test__/auth/authorize/authorize.spec.ts b/app/__test__/auth/authorize/authorize.spec.ts index 45349695..c0e04ff9 100644 --- a/app/__test__/auth/authorize/authorize.spec.ts +++ b/app/__test__/auth/authorize/authorize.spec.ts @@ -1,5 +1,5 @@ import { describe, expect, test } from "bun:test"; -import { Guard } from "../../../src/auth"; +import { Guard } from "../../../src/auth/authorize/Guard"; describe("authorize", () => { test("basic", async () => { diff --git a/app/__test__/core/Registry.spec.ts b/app/__test__/core/Registry.spec.ts index ee608b0d..cf9f68e8 100644 --- a/app/__test__/core/Registry.spec.ts +++ b/app/__test__/core/Registry.spec.ts @@ -1,6 +1,6 @@ import { describe, expect, test } from "bun:test"; -import { Registry } from "core"; -import { s } from "core/object/schema"; +import { Registry } from "core/registry/Registry"; +import { s } from "core/utils/schema"; type Constructor = new (...args: any[]) => T; diff --git a/app/__test__/core/object/SchemaObject.spec.ts b/app/__test__/core/object/SchemaObject.spec.ts index bedcb817..c73e0467 100644 --- a/app/__test__/core/object/SchemaObject.spec.ts +++ b/app/__test__/core/object/SchemaObject.spec.ts @@ -1,6 +1,6 @@ import { describe, expect, test } from "bun:test"; -import { SchemaObject } from "../../../src/core"; -import { s } from "core/object/schema"; +import { s } from "core/utils/schema"; +import { SchemaObject } from "core/object/SchemaObject"; describe("SchemaObject", async () => { test("basic", async () => { diff --git a/app/__test__/data/DataController.spec.ts b/app/__test__/data/DataController.spec.ts index da014ac6..ca4905d5 100644 --- a/app/__test__/data/DataController.spec.ts +++ b/app/__test__/data/DataController.spec.ts @@ -1,19 +1,16 @@ import { afterAll, beforeAll, describe, expect, test } from "bun:test"; -import { Guard } from "../../src/auth"; -import { parse } from "core/object/schema"; -import { - Entity, - type EntityData, - EntityManager, - ManyToOneRelation, - TextField, -} from "../../src/data"; +import { Guard } from "../../src/auth/authorize/Guard"; +import { parse } from "core/utils/schema"; + import { DataController } from "../../src/data/api/DataController"; import { dataConfigSchema } from "../../src/data/data-schema"; import { disableConsoleLog, enableConsoleLog, getDummyConnection } from "../helper"; import type { RepositoryResultJSON } from "data/entities/query/RepositoryResult"; import type { MutatorResultJSON } from "data/entities/mutation/MutatorResult"; +import { Entity, EntityManager, type EntityData } from "data/entities"; +import { TextField } from "data/fields"; +import { ManyToOneRelation } from "data/relations"; const { dummyConnection, afterAllCleanup } = getDummyConnection(); beforeAll(() => disableConsoleLog(["log", "warn"])); diff --git a/app/__test__/data/data.test.ts b/app/__test__/data/data.test.ts index 8032167f..10cf8ac9 100644 --- a/app/__test__/data/data.test.ts +++ b/app/__test__/data/data.test.ts @@ -1,12 +1,6 @@ import { afterAll, describe, expect, test } from "bun:test"; -import { - Entity, - EntityManager, - NumberField, - PrimaryField, - Repository, - TextField, -} from "../../src/data"; +import { Entity, EntityManager } from "data/entities"; +import { TextField, PrimaryField, NumberField } from "data/fields"; import { getDummyConnection } from "./helper"; const { dummyConnection, afterAllCleanup } = getDummyConnection(); diff --git a/app/__test__/data/helper.ts b/app/__test__/data/helper.ts index df1ed889..09229dad 100644 --- a/app/__test__/data/helper.ts +++ b/app/__test__/data/helper.ts @@ -2,7 +2,7 @@ import { unlink } from "node:fs/promises"; import type { SqliteDatabase } from "kysely"; // @ts-ignore import Database from "libsql"; -import { SqliteLocalConnection } from "../../src/data"; +import { SqliteLocalConnection } from "data/connection/sqlite/SqliteLocalConnection"; export function getDummyDatabase(memory: boolean = true): { dummyDb: SqliteDatabase; diff --git a/app/__test__/data/mutation.relation.test.ts b/app/__test__/data/mutation.relation.test.ts index 57124b23..346355c0 100644 --- a/app/__test__/data/mutation.relation.test.ts +++ b/app/__test__/data/mutation.relation.test.ts @@ -1,13 +1,10 @@ // eslint-disable-next-line import/no-unresolved import { afterAll, describe, expect, test } from "bun:test"; -import { - Entity, - EntityManager, - ManyToOneRelation, - NumberField, - SchemaManager, - TextField, -} from "../../src/data"; +import { Entity } from "data/entities"; +import { EntityManager } from "data/entities/EntityManager"; +import { ManyToOneRelation } from "data/relations"; +import { NumberField, TextField } from "data/fields"; +import { SchemaManager } from "data/schema/SchemaManager"; import { getDummyConnection } from "./helper"; const { dummyConnection, afterAllCleanup } = getDummyConnection(); diff --git a/app/__test__/data/mutation.simple.test.ts b/app/__test__/data/mutation.simple.test.ts index 93b17da2..e5e65ee0 100644 --- a/app/__test__/data/mutation.simple.test.ts +++ b/app/__test__/data/mutation.simple.test.ts @@ -1,7 +1,8 @@ // eslint-disable-next-line import/no-unresolved import { afterAll, describe, expect, test } from "bun:test"; -import { Entity, EntityManager, Mutator, NumberField, TextField } from "../../src/data"; -import { TransformPersistFailedException } from "../../src/data/errors"; +import { Entity, EntityManager } from "data/entities"; +import { NumberField, TextField } from "data/fields"; +import { TransformPersistFailedException } from "data/errors"; import { getDummyConnection } from "./helper"; const { dummyConnection, afterAllCleanup } = getDummyConnection(); diff --git a/app/__test__/data/polymorphic.test.ts b/app/__test__/data/polymorphic.test.ts index 9b51f8c7..5777801b 100644 --- a/app/__test__/data/polymorphic.test.ts +++ b/app/__test__/data/polymorphic.test.ts @@ -1,6 +1,8 @@ import { afterAll, expect as bunExpect, describe, test } from "bun:test"; -import { stripMark } from "core/object/schema"; -import { Entity, EntityManager, PolymorphicRelation, TextField } from "../../src/data"; +import { stripMark } from "core/utils/schema"; +import { Entity, EntityManager } from "data/entities"; +import { TextField } from "data/fields"; +import { PolymorphicRelation } from "data/relations"; import { getDummyConnection } from "./helper"; const { dummyConnection, afterAllCleanup } = getDummyConnection(); diff --git a/app/__test__/data/prototype.test.ts b/app/__test__/data/prototype.test.ts index e0dfc7cc..843dc1c5 100644 --- a/app/__test__/data/prototype.test.ts +++ b/app/__test__/data/prototype.test.ts @@ -2,19 +2,20 @@ import { describe, expect, test } from "bun:test"; import { BooleanField, DateField, - Entity, - EntityIndex, - EntityManager, EnumField, JsonField, + NumberField, + TextField, + EntityIndex, +} from "data/fields"; +import { Entity, EntityManager } from "data/entities"; +import { ManyToManyRelation, ManyToOneRelation, - NumberField, OneToOneRelation, PolymorphicRelation, - TextField, -} from "../../src/data"; -import { DummyConnection } from "../../src/data/connection/DummyConnection"; +} from "data/relations"; +import { DummyConnection } from "data/connection/DummyConnection"; import { FieldPrototype, type FieldSchema, @@ -32,8 +33,8 @@ import { number, relation, text, -} from "../../src/data/prototype"; -import { MediaField } from "../../src/media/MediaField"; +} from "data/prototype"; +import { MediaField } from "media/MediaField"; describe("prototype", () => { test("...", () => { @@ -296,9 +297,9 @@ describe("prototype", () => { new Entity("posts", [new TextField("name"), new TextField("slug", { required: true })]), new Entity("comments", [new TextField("some")]), new Entity("users", [new TextField("email")]), - ]; + ] as const; const _em2 = new EntityManager( - es, + [...es], new DummyConnection(), [new ManyToOneRelation(es[0], es[1]), new ManyToOneRelation(es[0], es[2])], [ diff --git a/app/__test__/data/relations.test.ts b/app/__test__/data/relations.test.ts index 07ecd199..9080c45a 100644 --- a/app/__test__/data/relations.test.ts +++ b/app/__test__/data/relations.test.ts @@ -1,13 +1,14 @@ // eslint-disable-next-line import/no-unresolved import { afterAll, describe, expect, test } from "bun:test"; -import { Entity, EntityManager, TextField } from "../../src/data"; +import { Entity, EntityManager } from "data/entities"; +import { TextField } from "data/fields"; import { ManyToManyRelation, ManyToOneRelation, OneToOneRelation, PolymorphicRelation, RelationField, -} from "../../src/data/relations"; +} from "data/relations"; import { getDummyConnection } from "./helper"; const { dummyConnection, afterAllCleanup } = getDummyConnection(); @@ -77,7 +78,7 @@ describe("Relations", async () => { const em = new EntityManager(entities, dummyConnection, relations); // verify naming - const rel = em.relations.all[0]; + const rel = em.relations.all[0]!; expect(rel.source.entity.name).toBe(posts.name); expect(rel.source.reference).toBe(posts.name); expect(rel.target.entity.name).toBe(users.name); @@ -89,11 +90,11 @@ describe("Relations", async () => { // verify low level relation expect(em.relationsOf(users.name).length).toBe(1); expect(em.relationsOf(users.name).length).toBe(1); - expect(em.relationsOf(users.name)[0].source.entity).toBe(posts); + expect(em.relationsOf(users.name)[0]!.source.entity).toBe(posts); expect(posts.field("author_id")).toBeInstanceOf(RelationField); expect(em.relationsOf(users.name).length).toBe(1); expect(em.relationsOf(users.name).length).toBe(1); - expect(em.relationsOf(users.name)[0].source.entity).toBe(posts); + expect(em.relationsOf(users.name)[0]!.source.entity).toBe(posts); // verify high level relation (from users) const userPostsRel = em.relationOf(users.name, "posts"); @@ -191,7 +192,7 @@ describe("Relations", async () => { const em = new EntityManager(entities, dummyConnection, relations); // verify naming - const rel = em.relations.all[0]; + const rel = em.relations.all[0]!; expect(rel.source.entity.name).toBe(users.name); expect(rel.source.reference).toBe(users.name); expect(rel.target.entity.name).toBe(settings.name); @@ -202,8 +203,8 @@ describe("Relations", async () => { expect(em.relationsOf(users.name).length).toBe(1); expect(em.relationsOf(users.name).length).toBe(1); - expect(em.relationsOf(users.name)[0].source.entity).toBe(users); - expect(em.relationsOf(users.name)[0].target.entity).toBe(settings); + expect(em.relationsOf(users.name)[0]!.source.entity).toBe(users); + expect(em.relationsOf(users.name)[0]!.target.entity).toBe(settings); // verify high level relation (from users) const userSettingRel = em.relationOf(users.name, settings.name); @@ -323,7 +324,7 @@ describe("Relations", async () => { ); // mutation info - expect(relations[0].helper(posts.name)!.getMutationInfo()).toEqual({ + expect(relations[0]!.helper(posts.name)!.getMutationInfo()).toEqual({ reference: "categories", local_field: undefined, $set: false, @@ -334,7 +335,7 @@ describe("Relations", async () => { cardinality: undefined, relation_type: "m:n", }); - expect(relations[0].helper(categories.name)!.getMutationInfo()).toEqual({ + expect(relations[0]!.helper(categories.name)!.getMutationInfo()).toEqual({ reference: "posts", local_field: undefined, $set: false, diff --git a/app/__test__/data/specs/Entity.spec.ts b/app/__test__/data/specs/Entity.spec.ts index 033d10b5..064db2d2 100644 --- a/app/__test__/data/specs/Entity.spec.ts +++ b/app/__test__/data/specs/Entity.spec.ts @@ -1,6 +1,6 @@ import { describe, expect, test } from "bun:test"; -import { Entity, NumberField, TextField } from "data"; -import * as p from "data/prototype"; +import { Entity } from "data/entities"; +import { NumberField, TextField } from "data/fields"; describe("[data] Entity", async () => { const entity = new Entity("test", [ diff --git a/app/__test__/data/specs/EntityManager.spec.ts b/app/__test__/data/specs/EntityManager.spec.ts index 64019959..52cd76ff 100644 --- a/app/__test__/data/specs/EntityManager.spec.ts +++ b/app/__test__/data/specs/EntityManager.spec.ts @@ -1,12 +1,8 @@ import { afterAll, describe, expect, test } from "bun:test"; -import { - Entity, - EntityManager, - ManyToManyRelation, - ManyToOneRelation, - SchemaManager, -} from "../../../src/data"; -import { UnableToConnectException } from "../../../src/data/errors"; +import { Entity, EntityManager } from "data/entities"; +import { ManyToManyRelation, ManyToOneRelation } from "data/relations"; +import { SchemaManager } from "data/schema/SchemaManager"; +import { UnableToConnectException } from "data/errors"; import { getDummyConnection } from "../helper"; const { dummyConnection, afterAllCleanup } = getDummyConnection(); diff --git a/app/__test__/data/specs/JoinBuilder.spec.ts b/app/__test__/data/specs/JoinBuilder.spec.ts index 9260aeb8..16f8d306 100644 --- a/app/__test__/data/specs/JoinBuilder.spec.ts +++ b/app/__test__/data/specs/JoinBuilder.spec.ts @@ -1,6 +1,8 @@ import { afterAll, describe, expect, test } from "bun:test"; -import { Entity, EntityManager, ManyToOneRelation, TextField } from "../../../src/data"; -import { JoinBuilder } from "../../../src/data/entities/query/JoinBuilder"; +import { Entity, EntityManager } from "data/entities"; +import { ManyToOneRelation } from "data/relations"; +import { TextField } from "data/fields"; +import { JoinBuilder } from "data/entities/query/JoinBuilder"; import { getDummyConnection } from "../helper"; const { dummyConnection, afterAllCleanup } = getDummyConnection(); diff --git a/app/__test__/data/specs/Mutator.spec.ts b/app/__test__/data/specs/Mutator.spec.ts index d7f09e02..45bbb282 100644 --- a/app/__test__/data/specs/Mutator.spec.ts +++ b/app/__test__/data/specs/Mutator.spec.ts @@ -1,18 +1,16 @@ import { afterAll, beforeAll, describe, expect, test } from "bun:test"; import type { EventManager } from "../../../src/core/events"; +import { Entity, EntityManager } from "data/entities"; import { - Entity, - EntityManager, ManyToOneRelation, - MutatorEvents, - NumberField, OneToOneRelation, - type RelationField, + RelationField, RelationMutator, - TextField, -} from "../../../src/data"; -import * as proto from "../../../src/data/prototype"; +} from "data/relations"; +import { NumberField, TextField } from "data/fields"; +import * as proto from "data/prototype"; import { getDummyConnection, disableConsoleLog, enableConsoleLog } from "../../helper"; +import { MutatorEvents } from "data/events"; const { dummyConnection, afterAllCleanup } = getDummyConnection(); afterAll(afterAllCleanup); diff --git a/app/__test__/data/specs/Repository.spec.ts b/app/__test__/data/specs/Repository.spec.ts index 46bbf7ba..35c4ec56 100644 --- a/app/__test__/data/specs/Repository.spec.ts +++ b/app/__test__/data/specs/Repository.spec.ts @@ -1,17 +1,10 @@ import { afterAll, describe, expect, test } from "bun:test"; import type { Kysely, Transaction } from "kysely"; -import { Perf } from "core/utils"; -import { - Entity, - EntityManager, - LibsqlConnection, - ManyToOneRelation, - RepositoryEvents, - TextField, - entity as $entity, - text as $text, - em as $em, -} from "data"; +import { TextField } from "data/fields"; +import { em as $em, entity as $entity, text as $text } from "data/prototype"; +import { Entity, EntityManager } from "data/entities"; +import { ManyToOneRelation } from "data/relations"; +import { RepositoryEvents } from "data/events"; import { getDummyConnection } from "../helper"; type E = Kysely | Transaction; diff --git a/app/__test__/data/specs/SchemaManager.spec.ts b/app/__test__/data/specs/SchemaManager.spec.ts index 7c2322b9..679010ba 100644 --- a/app/__test__/data/specs/SchemaManager.spec.ts +++ b/app/__test__/data/specs/SchemaManager.spec.ts @@ -1,7 +1,9 @@ // eslint-disable-next-line import/no-unresolved import { afterAll, describe, expect, test } from "bun:test"; -import { randomString } from "../../../src/core/utils"; -import { Entity, EntityIndex, EntityManager, SchemaManager, TextField } from "../../../src/data"; +import { randomString } from "core/utils"; +import { Entity, EntityManager } from "data/entities"; +import { TextField, EntityIndex } from "data/fields"; +import { SchemaManager } from "data/schema/SchemaManager"; import { getDummyConnection } from "../helper"; const { dummyConnection, afterAllCleanup } = getDummyConnection(); diff --git a/app/__test__/data/specs/WhereBuilder.spec.ts b/app/__test__/data/specs/WhereBuilder.spec.ts index a477cbed..98b822c1 100644 --- a/app/__test__/data/specs/WhereBuilder.spec.ts +++ b/app/__test__/data/specs/WhereBuilder.spec.ts @@ -1,6 +1,6 @@ import { describe, test, expect } from "bun:test"; import { getDummyConnection } from "../helper"; -import { type WhereQuery, WhereBuilder } from "data"; +import { WhereBuilder, type WhereQuery } from "data/entities/query/WhereBuilder"; function qb() { const c = getDummyConnection(); diff --git a/app/__test__/data/specs/WithBuilder.spec.ts b/app/__test__/data/specs/WithBuilder.spec.ts index 568cf1fb..31cfd969 100644 --- a/app/__test__/data/specs/WithBuilder.spec.ts +++ b/app/__test__/data/specs/WithBuilder.spec.ts @@ -1,14 +1,9 @@ import { describe, expect, test } from "bun:test"; -import { - Entity, - EntityManager, - ManyToManyRelation, - ManyToOneRelation, - PolymorphicRelation, - TextField, - WithBuilder, -} from "../../../src/data"; -import * as proto from "../../../src/data/prototype"; +import { Entity, EntityManager } from "data/entities"; +import { ManyToManyRelation, ManyToOneRelation, PolymorphicRelation } from "data/relations"; +import { TextField } from "data/fields"; +import * as proto from "data/prototype"; +import { WithBuilder } from "data/entities/query/WithBuilder"; import { schemaToEm } from "../../helper"; import { getDummyConnection } from "../helper"; diff --git a/app/__test__/data/specs/connection/Connection.spec.ts b/app/__test__/data/specs/connection/Connection.spec.ts index d3e8226c..85ed6679 100644 --- a/app/__test__/data/specs/connection/Connection.spec.ts +++ b/app/__test__/data/specs/connection/Connection.spec.ts @@ -1,5 +1,5 @@ import { afterAll, describe, expect, test } from "bun:test"; -import { EntityManager } from "../../../../src/data"; +import { EntityManager } from "data/entities/EntityManager"; import { getDummyConnection } from "../../helper"; const { dummyConnection, afterAllCleanup } = getDummyConnection(); diff --git a/app/__test__/data/specs/fields/BooleanField.spec.ts b/app/__test__/data/specs/fields/BooleanField.spec.ts index a061e1fd..10de95a4 100644 --- a/app/__test__/data/specs/fields/BooleanField.spec.ts +++ b/app/__test__/data/specs/fields/BooleanField.spec.ts @@ -1,9 +1,10 @@ +import { bunTestRunner } from "adapter/bun/test"; import { describe, expect, test } from "bun:test"; -import { BooleanField } from "../../../../src/data"; +import { BooleanField } from "data/fields"; import { fieldTestSuite, transformPersist } from "data/fields/field-test-suite"; describe("[data] BooleanField", async () => { - fieldTestSuite({ expect, test }, BooleanField, { defaultValue: true, schemaType: "boolean" }); + fieldTestSuite(bunTestRunner, BooleanField, { defaultValue: true, schemaType: "boolean" }); test("transformRetrieve", async () => { const field = new BooleanField("test"); diff --git a/app/__test__/data/specs/fields/DateField.spec.ts b/app/__test__/data/specs/fields/DateField.spec.ts index dc548468..142c6d3d 100644 --- a/app/__test__/data/specs/fields/DateField.spec.ts +++ b/app/__test__/data/specs/fields/DateField.spec.ts @@ -1,5 +1,5 @@ -import { describe, expect, test } from "bun:test"; -import { DateField, dateFieldConfigSchema } from "../../../../src/data"; +import { describe, test } from "bun:test"; +import { DateField } from "data/fields"; import { fieldTestSuite } from "data/fields/field-test-suite"; import { bunTestRunner } from "adapter/bun/test"; diff --git a/app/__test__/data/specs/fields/EnumField.spec.ts b/app/__test__/data/specs/fields/EnumField.spec.ts index 066dd882..3bce5e60 100644 --- a/app/__test__/data/specs/fields/EnumField.spec.ts +++ b/app/__test__/data/specs/fields/EnumField.spec.ts @@ -1,5 +1,6 @@ +import { bunTestRunner } from "adapter/bun/test"; import { describe, expect, test } from "bun:test"; -import { EnumField } from "../../../../src/data"; +import { EnumField } from "data/fields"; import { fieldTestSuite, transformPersist } from "data/fields/field-test-suite"; function options(strings: string[]) { @@ -8,7 +9,7 @@ function options(strings: string[]) { describe("[data] EnumField", async () => { fieldTestSuite( - { expect, test }, + bunTestRunner, // @ts-ignore EnumField, { defaultValue: "a", schemaType: "text" }, diff --git a/app/__test__/data/specs/fields/Field.spec.ts b/app/__test__/data/specs/fields/Field.spec.ts index 41707a84..6c8245b4 100644 --- a/app/__test__/data/specs/fields/Field.spec.ts +++ b/app/__test__/data/specs/fields/Field.spec.ts @@ -2,7 +2,7 @@ import { describe, expect, test } from "bun:test"; import { baseFieldConfigSchema, Field } from "../../../../src/data/fields/Field"; import { fieldTestSuite } from "data/fields/field-test-suite"; import { bunTestRunner } from "adapter/bun/test"; -import { stripMark } from "core/object/schema"; +import { stripMark } from "core/utils/schema"; describe("[data] Field", async () => { class FieldSpec extends Field { diff --git a/app/__test__/data/specs/fields/FieldIndex.spec.ts b/app/__test__/data/specs/fields/FieldIndex.spec.ts index ecc5e0c1..ab942146 100644 --- a/app/__test__/data/specs/fields/FieldIndex.spec.ts +++ b/app/__test__/data/specs/fields/FieldIndex.spec.ts @@ -1,6 +1,7 @@ import { describe, expect, test } from "bun:test"; -import { Entity, EntityIndex, Field } from "../../../../src/data"; -import { s } from "core/object/schema"; +import { Entity } from "data/entities"; +import { Field, EntityIndex } from "data/fields"; +import { s } from "core/utils/schema"; class TestField extends Field { protected getSchema(): any { diff --git a/app/__test__/data/specs/fields/JsonField.spec.ts b/app/__test__/data/specs/fields/JsonField.spec.ts index 0bc0d3b1..ff94dc3b 100644 --- a/app/__test__/data/specs/fields/JsonField.spec.ts +++ b/app/__test__/data/specs/fields/JsonField.spec.ts @@ -1,10 +1,11 @@ +import { bunTestRunner } from "adapter/bun/test"; import { describe, expect, test } from "bun:test"; -import { JsonField } from "../../../../src/data"; +import { JsonField } from "data/fields"; import { fieldTestSuite, transformPersist } from "data/fields/field-test-suite"; describe("[data] JsonField", async () => { const field = new JsonField("test"); - fieldTestSuite({ expect, test }, JsonField, { + fieldTestSuite(bunTestRunner, JsonField, { defaultValue: { a: 1 }, sampleValues: ["string", { test: 1 }, 1], schemaType: "text", diff --git a/app/__test__/data/specs/fields/JsonSchemaField.spec.ts b/app/__test__/data/specs/fields/JsonSchemaField.spec.ts index 77700986..809cb5ea 100644 --- a/app/__test__/data/specs/fields/JsonSchemaField.spec.ts +++ b/app/__test__/data/specs/fields/JsonSchemaField.spec.ts @@ -1,5 +1,5 @@ import { describe, expect, test } from "bun:test"; -import { JsonSchemaField } from "../../../../src/data"; +import { JsonSchemaField } from "data/fields"; import { fieldTestSuite } from "data/fields/field-test-suite"; describe("[data] JsonSchemaField", async () => { diff --git a/app/__test__/data/specs/fields/NumberField.spec.ts b/app/__test__/data/specs/fields/NumberField.spec.ts index e46c075e..ae50c108 100644 --- a/app/__test__/data/specs/fields/NumberField.spec.ts +++ b/app/__test__/data/specs/fields/NumberField.spec.ts @@ -1,5 +1,6 @@ +import { bunTestRunner } from "adapter/bun/test"; import { describe, expect, test } from "bun:test"; -import { NumberField } from "../../../../src/data"; +import { NumberField } from "data/fields"; import { fieldTestSuite, transformPersist } from "data/fields/field-test-suite"; describe("[data] NumberField", async () => { @@ -15,5 +16,5 @@ describe("[data] NumberField", async () => { expect(transformPersist(field2, 10000)).resolves.toBe(10000); }); - fieldTestSuite({ expect, test }, NumberField, { defaultValue: 12, schemaType: "integer" }); + fieldTestSuite(bunTestRunner, NumberField, { defaultValue: 12, schemaType: "integer" }); }); diff --git a/app/__test__/data/specs/fields/PrimaryField.spec.ts b/app/__test__/data/specs/fields/PrimaryField.spec.ts index c40ee149..cd1aafc8 100644 --- a/app/__test__/data/specs/fields/PrimaryField.spec.ts +++ b/app/__test__/data/specs/fields/PrimaryField.spec.ts @@ -1,5 +1,5 @@ import { describe, expect, test } from "bun:test"; -import { PrimaryField } from "../../../../src/data"; +import { PrimaryField } from "data/fields"; describe("[data] PrimaryField", async () => { const field = new PrimaryField("primary"); diff --git a/app/__test__/data/specs/fields/TextField.spec.ts b/app/__test__/data/specs/fields/TextField.spec.ts index 6a1bb96a..ee69cef8 100644 --- a/app/__test__/data/specs/fields/TextField.spec.ts +++ b/app/__test__/data/specs/fields/TextField.spec.ts @@ -1,5 +1,5 @@ import { describe, expect, test } from "bun:test"; -import { TextField, textFieldConfigSchema } from "../../../../src/data"; +import { TextField } from "data/fields"; import { fieldTestSuite, transformPersist } from "data/fields/field-test-suite"; import { bunTestRunner } from "adapter/bun/test"; diff --git a/app/__test__/data/specs/relations/EntityRelation.spec.ts b/app/__test__/data/specs/relations/EntityRelation.spec.ts index 62561b6d..489a5be0 100644 --- a/app/__test__/data/specs/relations/EntityRelation.spec.ts +++ b/app/__test__/data/specs/relations/EntityRelation.spec.ts @@ -1,11 +1,11 @@ import { describe, expect, it, test } from "bun:test"; -import { Entity, type EntityManager } from "../../../../src/data"; +import { Entity, type EntityManager } from "data/entities"; import { type BaseRelationConfig, EntityRelation, EntityRelationAnchor, RelationTypes, -} from "../../../../src/data/relations"; +} from "data/relations"; class TestEntityRelation extends EntityRelation { constructor(config?: BaseRelationConfig) { @@ -24,11 +24,11 @@ class TestEntityRelation extends EntityRelation { return this; } - buildWith(a: any, b: any, c: any): any { + buildWith(): any { return; } - buildJoin(a: any, b: any): any { + buildJoin(): any { return; } } diff --git a/app/__test__/flows/SubWorkflowTask.spec.ts b/app/__test__/flows/SubWorkflowTask.spec.ts index 7d70ebf6..2c8924af 100644 --- a/app/__test__/flows/SubWorkflowTask.spec.ts +++ b/app/__test__/flows/SubWorkflowTask.spec.ts @@ -1,6 +1,6 @@ import { describe, expect, test } from "bun:test"; import { Flow, LogTask, SubFlowTask, RenderTask, Task } from "../../src/flows"; -import { s } from "core/object/schema"; +import { s } from "core/utils/schema"; export class StringifyTask extends Task< typeof StringifyTask.schema, diff --git a/app/__test__/flows/Task.spec.ts b/app/__test__/flows/Task.spec.ts index b8e451cf..3f7674aa 100644 --- a/app/__test__/flows/Task.spec.ts +++ b/app/__test__/flows/Task.spec.ts @@ -1,7 +1,7 @@ import { describe, expect, test } from "bun:test"; import { Task } from "../../src/flows"; import { dynamic } from "../../src/flows/tasks/Task"; -import { s } from "core/object/schema"; +import { s } from "core/utils/schema"; describe.skip("Task", async () => { test("resolveParams: template with parse", async () => { diff --git a/app/__test__/flows/inputs.test.ts b/app/__test__/flows/inputs.test.ts index 5e8bb5d2..575bbc41 100644 --- a/app/__test__/flows/inputs.test.ts +++ b/app/__test__/flows/inputs.test.ts @@ -1,7 +1,7 @@ import { describe, expect, test } from "bun:test"; import { Hono } from "hono"; import { Event, EventManager } from "../../src/core/events"; -import { s, parse } from "core/object/schema"; +import { s, parse } from "core/utils/schema"; import { EventTrigger, Flow, HttpTrigger, type InputsMap, Task } from "../../src/flows"; import { dynamic } from "../../src/flows/tasks/Task"; diff --git a/app/__test__/flows/workflow-basic.test.ts b/app/__test__/flows/workflow-basic.test.ts index 48c0b9e6..b766ef8c 100644 --- a/app/__test__/flows/workflow-basic.test.ts +++ b/app/__test__/flows/workflow-basic.test.ts @@ -2,7 +2,7 @@ import { describe, expect, test } from "bun:test"; import { isEqual } from "lodash-es"; import { _jsonp, withDisabledConsole } from "../../src/core/utils"; -import { s } from "core/object/schema"; +import { s } from "core/utils/schema"; import { Condition, ExecutionEvent, FetchTask, Flow, LogTask, Task } from "../../src/flows"; /*beforeAll(disableConsoleLog); diff --git a/app/__test__/helper.ts b/app/__test__/helper.ts index ba09d4cb..1760d324 100644 --- a/app/__test__/helper.ts +++ b/app/__test__/helper.ts @@ -2,11 +2,12 @@ import { unlink } from "node:fs/promises"; import type { SelectQueryBuilder, SqliteDatabase } from "kysely"; import Database from "libsql"; import { format as sqlFormat } from "sql-formatter"; -import { type Connection, EntityManager, SqliteLocalConnection } from "../src/data"; import type { em as protoEm } from "../src/data/prototype"; import { writeFile } from "node:fs/promises"; import { join } from "node:path"; import { slugify } from "core/utils/strings"; +import { type Connection, SqliteLocalConnection } from "data/connection"; +import { EntityManager } from "data/entities/EntityManager"; export function getDummyDatabase(memory: boolean = true): { dummyDb: SqliteDatabase; diff --git a/app/__test__/media/MediaController.spec.ts b/app/__test__/media/MediaController.spec.ts index 9ec23557..7c9ae9f3 100644 --- a/app/__test__/media/MediaController.spec.ts +++ b/app/__test__/media/MediaController.spec.ts @@ -54,7 +54,6 @@ describe("MediaController", () => { body: file, }); const result = (await res.json()) as any; - console.log(result); expect(result.name).toBe(name); const destFile = Bun.file(assetsTmpPath + "/" + name); diff --git a/app/__test__/media/Storage.spec.ts b/app/__test__/media/Storage.spec.ts index 12341238..b055e11e 100644 --- a/app/__test__/media/Storage.spec.ts +++ b/app/__test__/media/Storage.spec.ts @@ -1,7 +1,7 @@ import { describe, expect, test } from "bun:test"; import { type FileBody, Storage } from "../../src/media/storage/Storage"; import * as StorageEvents from "../../src/media/storage/events"; -import { StorageAdapter } from "media"; +import { StorageAdapter } from "media/storage/StorageAdapter"; class TestAdapter extends StorageAdapter { files: Record = {}; diff --git a/app/__test__/modules/AppAuth.spec.ts b/app/__test__/modules/AppAuth.spec.ts index db43f74a..e523fbc4 100644 --- a/app/__test__/modules/AppAuth.spec.ts +++ b/app/__test__/modules/AppAuth.spec.ts @@ -1,10 +1,9 @@ import { afterAll, beforeAll, beforeEach, describe, expect, spyOn, test } from "bun:test"; import { createApp } from "core/test/utils"; import { AuthController } from "../../src/auth/api/AuthController"; -import { em, entity, make, text } from "../../src/data"; -import { AppAuth, type ModuleBuildContext } from "../../src/modules"; +import { em, entity, make, text } from "data/prototype"; +import { AppAuth, type ModuleBuildContext } from "modules"; import { disableConsoleLog, enableConsoleLog } from "../helper"; -// @ts-ignore import { makeCtx, moduleTestSuite } from "./module-test-suite"; describe("AppAuth", () => { diff --git a/app/__test__/modules/AppData.spec.ts b/app/__test__/modules/AppData.spec.ts index 8eca77b5..96225838 100644 --- a/app/__test__/modules/AppData.spec.ts +++ b/app/__test__/modules/AppData.spec.ts @@ -1,5 +1,5 @@ import { beforeEach, describe, expect, test } from "bun:test"; -import { parse } from "core/object/schema"; +import { parse } from "core/utils/schema"; import { fieldsSchema } from "../../src/data/data-schema"; import { AppData, type ModuleBuildContext } from "../../src/modules"; import { makeCtx, moduleTestSuite } from "./module-test-suite"; diff --git a/app/__test__/modules/AppMedia.spec.ts b/app/__test__/modules/AppMedia.spec.ts index 8fc5ffba..d09041b0 100644 --- a/app/__test__/modules/AppMedia.spec.ts +++ b/app/__test__/modules/AppMedia.spec.ts @@ -1,10 +1,9 @@ import { describe, expect, test } from "bun:test"; -import { registries } from "../../src"; import { createApp } from "core/test/utils"; -import { em, entity, text } from "../../src/data"; +import { em, entity, text } from "data/prototype"; +import { registries } from "modules/registries"; import { StorageLocalAdapter } from "adapter/node/storage/StorageLocalAdapter"; import { AppMedia } from "../../src/media/AppMedia"; -import { mediaConfigSchema } from "../../src/media/media-schema"; import { moduleTestSuite } from "./module-test-suite"; describe("AppMedia", () => { diff --git a/app/__test__/modules/Module.spec.ts b/app/__test__/modules/Module.spec.ts index 224da909..6033c5a6 100644 --- a/app/__test__/modules/Module.spec.ts +++ b/app/__test__/modules/Module.spec.ts @@ -1,6 +1,7 @@ import { describe, expect, test } from "bun:test"; -import { s, stripMark } from "core/object/schema"; -import { EntityManager, em, entity, index, text } from "../../src/data"; +import { s, stripMark } from "core/utils/schema"; +import { em, entity, index, text } from "data/prototype"; +import { EntityManager } from "data/entities/EntityManager"; import { DummyConnection } from "../../src/data/connection/DummyConnection"; import { Module } from "../../src/modules/Module"; import { ModuleHelper } from "modules/ModuleHelper"; diff --git a/app/__test__/modules/ModuleManager.spec.ts b/app/__test__/modules/ModuleManager.spec.ts index 876c5827..9c24de01 100644 --- a/app/__test__/modules/ModuleManager.spec.ts +++ b/app/__test__/modules/ModuleManager.spec.ts @@ -1,11 +1,13 @@ import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test"; import { disableConsoleLog, enableConsoleLog } from "core/utils"; -import { Connection, entity, text } from "data"; + import { Module } from "modules/Module"; import { type ConfigTable, getDefaultConfig, ModuleManager } from "modules/ModuleManager"; import { CURRENT_VERSION, TABLE_NAME } from "modules/migrations"; import { getDummyConnection } from "../helper"; -import { s, stripMark } from "core/object/schema"; +import { s, stripMark } from "core/utils/schema"; +import { Connection } from "data/connection/Connection"; +import { entity, text } from "data/prototype"; describe("ModuleManager", async () => { test("s1: no config, no build", async () => { diff --git a/app/__test__/modules/module-test-suite.ts b/app/__test__/modules/module-test-suite.ts index 2a514fb0..99dfcf51 100644 --- a/app/__test__/modules/module-test-suite.ts +++ b/app/__test__/modules/module-test-suite.ts @@ -1,11 +1,11 @@ import { beforeEach, describe, expect, it } from "bun:test"; import { Hono } from "hono"; -import { Guard } from "../../src/auth"; -import { DebugLogger } from "../../src/core"; -import { EventManager } from "../../src/core/events"; -import { EntityManager } from "../../src/data"; -import { Module, type ModuleBuildContext } from "../../src/modules/Module"; +import { Guard } from "auth/authorize/Guard"; +import { DebugLogger } from "core/utils/DebugLogger"; +import { EventManager } from "core/events"; +import { EntityManager } from "data/entities/EntityManager"; +import { Module, type ModuleBuildContext } from "modules/Module"; import { getDummyConnection } from "../helper"; import { ModuleHelper } from "modules/ModuleHelper"; diff --git a/app/build.ts b/app/build.ts index ba3c11f4..9b2ba2ea 100644 --- a/app/build.ts +++ b/app/build.ts @@ -79,14 +79,7 @@ async function buildApi() { sourcemap, watch, define, - entry: [ - "src/index.ts", - "src/core/index.ts", - "src/core/utils/index.ts", - "src/data/index.ts", - "src/media/index.ts", - "src/plugins/index.ts", - ], + entry: ["src/index.ts", "src/core/utils/index.ts", "src/plugins/index.ts"], outDir: "dist", external: [...external], metafile: true, diff --git a/app/package.json b/app/package.json index 95d2a495..dcf8718a 100644 --- a/app/package.json +++ b/app/package.json @@ -13,6 +13,7 @@ "bugs": { "url": "https://github.com/bknd-io/bknd/issues" }, + "packageManager": "bun@1.2.19", "engines": { "node": ">=22" }, @@ -60,8 +61,7 @@ "bcryptjs": "^3.0.2", "dayjs": "^1.11.13", "fast-xml-parser": "^5.0.8", - "hono": "^4.7.11", - "json-schema-form-react": "^0.0.2", + "hono": "4.8.3", "json-schema-library": "10.0.0-rc7", "json-schema-to-ts": "^3.1.1", "kysely": "^0.27.6", @@ -100,7 +100,7 @@ "dotenv": "^16.4.7", "jotai": "^2.12.2", "jsdom": "^26.0.0", - "jsonv-ts": "^0.2.2", + "jsonv-ts": "^0.3.2", "kysely-d1": "^0.3.0", "kysely-generic-sqlite": "^1.2.1", "libsql-stateless-easy": "^1.8.0", @@ -161,16 +161,6 @@ "import": "./dist/ui/client/index.js", "require": "./dist/ui/client/index.js" }, - "./data": { - "types": "./dist/types/data/index.d.ts", - "import": "./dist/data/index.js", - "require": "./dist/data/index.js" - }, - "./core": { - "types": "./dist/types/core/index.d.ts", - "import": "./dist/core/index.js", - "require": "./dist/core/index.js" - }, "./utils": { "types": "./dist/types/core/utils/index.d.ts", "import": "./dist/core/utils/index.js", @@ -181,11 +171,6 @@ "import": "./dist/cli/index.js", "require": "./dist/cli/index.js" }, - "./media": { - "types": "./dist/types/media/index.d.ts", - "import": "./dist/media/index.js", - "require": "./dist/media/index.js" - }, "./plugins": { "types": "./dist/types/plugins/index.d.ts", "import": "./dist/plugins/index.js", @@ -251,15 +236,13 @@ }, "./dist/main.css": "./dist/ui/main.css", "./dist/styles.css": "./dist/ui/styles.css", - "./dist/manifest.json": "./dist/static/.vite/manifest.json" + "./dist/manifest.json": "./dist/static/.vite/manifest.json", + "./static/*": "./dist/static/*" }, "typesVersions": { "*": { - "data": ["./dist/types/data/index.d.ts"], - "core": ["./dist/types/core/index.d.ts"], "utils": ["./dist/types/core/utils/index.d.ts"], "cli": ["./dist/types/cli/index.d.ts"], - "media": ["./dist/types/media/index.d.ts"], "plugins": ["./dist/types/plugins/index.d.ts"], "adapter": ["./dist/types/adapter/index.d.ts"], "adapter/cloudflare": ["./dist/types/adapter/cloudflare/index.d.ts"], diff --git a/app/src/Api.ts b/app/src/Api.ts index 8c93d972..cce91562 100644 --- a/app/src/Api.ts +++ b/app/src/Api.ts @@ -1,4 +1,4 @@ -import type { SafeUser } from "auth"; +import type { SafeUser } from "bknd"; import { AuthApi, type AuthApiOptions } from "auth/api/AuthApi"; import { DataApi, type DataApiOptions } from "data/api/DataApi"; import { decode } from "hono/jwt"; diff --git a/app/src/adapter/bun/bun.adapter.ts b/app/src/adapter/bun/bun.adapter.ts index 5d7c1482..c3d271bd 100644 --- a/app/src/adapter/bun/bun.adapter.ts +++ b/app/src/adapter/bun/bun.adapter.ts @@ -3,10 +3,9 @@ import path from "node:path"; import { type RuntimeBkndConfig, createRuntimeApp, type RuntimeOptions } from "bknd/adapter"; import { registerLocalMediaAdapter } from "."; -import { config } from "bknd/core"; +import { config, type App } from "bknd"; import type { ServeOptions } from "bun"; import { serveStatic } from "hono/bun"; -import type { App } from "App"; type BunEnv = Bun.Env; export type BunBkndConfig = RuntimeBkndConfig & Omit; @@ -21,8 +20,8 @@ export async function createApp( return await createRuntimeApp( { - ...config, serveStatic: serveStatic({ root }), + ...config, }, args ?? (process.env as Env), opts, @@ -53,6 +52,7 @@ export function serve( onBuilt, buildConfig, adminOptions, + serveStatic, ...serveOptions }: BunBkndConfig = {}, args: Env = {} as Env, @@ -70,6 +70,7 @@ export function serve( buildConfig, adminOptions, distPath, + serveStatic, }, args, opts, diff --git a/app/src/adapter/bun/connection/BunSqliteConnection.ts b/app/src/adapter/bun/connection/BunSqliteConnection.ts index 900b01d0..08444c5e 100644 --- a/app/src/adapter/bun/connection/BunSqliteConnection.ts +++ b/app/src/adapter/bun/connection/BunSqliteConnection.ts @@ -1,5 +1,5 @@ import { Database } from "bun:sqlite"; -import { genericSqlite, type GenericSqliteConnection } from "bknd/data"; +import { genericSqlite, type GenericSqliteConnection } from "bknd"; export type BunSqliteConnection = GenericSqliteConnection; export type BunSqliteConnectionConfig = { diff --git a/app/src/adapter/cloudflare/bindings.ts b/app/src/adapter/cloudflare/bindings.ts index 82eca2ae..0b685248 100644 --- a/app/src/adapter/cloudflare/bindings.ts +++ b/app/src/adapter/cloudflare/bindings.ts @@ -12,7 +12,10 @@ export function getBindings(env: any, type: T): Bindin const bindings: BindingMap[] = []; for (const key in env) { try { - if (env[key] && (env[key] as any).constructor.name === type) { + if ( + env[key] && + ((env[key] as any).constructor.name === type || String(env[key]) === `[object ${type}]`) + ) { bindings.push({ key, value: env[key] as BindingTypeMap[T], diff --git a/app/src/adapter/cloudflare/config.ts b/app/src/adapter/cloudflare/config.ts index 8dbfff6c..da5af078 100644 --- a/app/src/adapter/cloudflare/config.ts +++ b/app/src/adapter/cloudflare/config.ts @@ -1,16 +1,16 @@ /// +import { Connection } from "bknd"; +import { sqlite } from "bknd/adapter/sqlite"; +import { makeConfig as makeAdapterConfig } from "bknd/adapter"; import { registerMedia } from "./storage/StorageR2Adapter"; import { getBinding } from "./bindings"; import { d1Sqlite } from "./connection/D1Connection"; -import { Connection } from "bknd/data"; import type { CloudflareBkndConfig, CloudflareEnv } from "."; import { App } from "bknd"; -import { makeConfig as makeAdapterConfig } from "bknd/adapter"; import type { Context, ExecutionContext } from "hono"; import { $console } from "core/utils"; import { setCookie } from "hono/cookie"; -import { sqlite } from "bknd/adapter/sqlite"; export const constants = { exec_async_event_id: "cf_register_waituntil", diff --git a/app/src/adapter/cloudflare/connection/D1Connection.ts b/app/src/adapter/cloudflare/connection/D1Connection.ts index 3462461c..f2c2b753 100644 --- a/app/src/adapter/cloudflare/connection/D1Connection.ts +++ b/app/src/adapter/cloudflare/connection/D1Connection.ts @@ -1,6 +1,6 @@ /// -import { genericSqlite, type GenericSqliteConnection } from "bknd/data"; +import { genericSqlite, type GenericSqliteConnection } from "bknd"; import type { QueryResult } from "kysely"; export type D1SqliteConnection = GenericSqliteConnection; diff --git a/app/src/adapter/cloudflare/connection/DoConnection.ts b/app/src/adapter/cloudflare/connection/DoConnection.ts index de7d2910..91ae5ec6 100644 --- a/app/src/adapter/cloudflare/connection/DoConnection.ts +++ b/app/src/adapter/cloudflare/connection/DoConnection.ts @@ -1,6 +1,6 @@ /// -import { genericSqlite, type GenericSqliteConnection } from "bknd/data"; +import { genericSqlite, type GenericSqliteConnection } from "bknd"; import type { QueryResult } from "kysely"; export type D1SqliteConnection = GenericSqliteConnection; diff --git a/app/src/adapter/cloudflare/index.ts b/app/src/adapter/cloudflare/index.ts index b8b3c4e8..bc4e294f 100644 --- a/app/src/adapter/cloudflare/index.ts +++ b/app/src/adapter/cloudflare/index.ts @@ -13,7 +13,7 @@ export { type BindingMap, } from "./bindings"; export { constants } from "./config"; -export { StorageR2Adapter } from "./storage/StorageR2Adapter"; +export { StorageR2Adapter, registerMedia } from "./storage/StorageR2Adapter"; export { registries } from "bknd"; // for compatibility with old code diff --git a/app/src/adapter/cloudflare/storage/StorageR2Adapter.ts b/app/src/adapter/cloudflare/storage/StorageR2Adapter.ts index 9fc41d41..a1edf58a 100644 --- a/app/src/adapter/cloudflare/storage/StorageR2Adapter.ts +++ b/app/src/adapter/cloudflare/storage/StorageR2Adapter.ts @@ -1,8 +1,7 @@ -import { registries } from "bknd"; -import { isDebug } from "bknd/core"; -import { guessMimeType as guess, StorageAdapter, type FileBody } from "bknd/media"; +import { registries, isDebug, guessMimeType } from "bknd"; import { getBindings } from "../bindings"; -import { s } from "core/object/schema"; +import { s } from "bknd/utils"; +import { StorageAdapter, type FileBody } from "bknd"; export function makeSchema(bindings: string[] = []) { return s.object( @@ -90,7 +89,7 @@ export class StorageR2Adapter extends StorageAdapter { const responseHeaders = new Headers({ "Accept-Ranges": "bytes", - "Content-Type": guess(key), + "Content-Type": guessMimeType(key), }); const range = headers.has("range"); @@ -142,7 +141,7 @@ export class StorageR2Adapter extends StorageAdapter { if (!metadata || Object.keys(metadata).length === 0) { // guessing is especially required for dev environment (miniflare) metadata = { - contentType: guess(object.key), + contentType: guessMimeType(object.key), }; } @@ -159,7 +158,7 @@ export class StorageR2Adapter extends StorageAdapter { } return { - type: String(head.httpMetadata?.contentType ?? guess(key)), + type: String(head.httpMetadata?.contentType ?? guessMimeType(key)), size: head.size, }; } diff --git a/app/src/adapter/index.ts b/app/src/adapter/index.ts index 9e74dd01..3082d43f 100644 --- a/app/src/adapter/index.ts +++ b/app/src/adapter/index.ts @@ -1,11 +1,8 @@ -import { App, type CreateAppConfig } from "bknd"; -import { config as $config } from "bknd/core"; +import { config as $config, App, type CreateAppConfig, Connection, guessMimeType } from "bknd"; import { $console } from "bknd/utils"; -import type { MiddlewareHandler } from "hono"; +import type { Context, MiddlewareHandler, Next } from "hono"; import type { AdminControllerOptions } from "modules/server/AdminController"; -import { Connection } from "bknd/data"; - -export { Connection } from "bknd/data"; +import type { Manifest } from "vite"; export type BkndConfig = CreateAppConfig & { app?: CreateAppConfig | ((args: Args) => CreateAppConfig); @@ -70,9 +67,9 @@ export async function createAdapterApp( return app; } + +/** + * Creates a middleware handler to serve static assets via dynamic imports. + * This is useful for environments where filesystem access is limited but bundled assets can be imported. + * + * @param manifest - Vite manifest object containing asset information + * @returns Hono middleware handler for serving static assets + * + * @example + * ```typescript + * import { serveStaticViaImport } from "bknd/adapter"; + * + * serve({ + * serveStatic: serveStaticViaImport(), + * }); + * ``` + */ +export function serveStaticViaImport(opts?: { manifest?: Manifest }) { + let files: string[] | undefined; + + // @ts-ignore + return async (c: Context, next: Next) => { + if (!files) { + const manifest = + opts?.manifest || ((await import("bknd/dist/manifest.json")).default as Manifest); + files = Object.values(manifest).flatMap((asset) => [asset.file, ...(asset.css || [])]); + } + + const path = c.req.path.substring(1); + if (files.includes(path)) { + try { + const content = await import(`bknd/static/${path}?raw`, { + assert: { type: "text" }, + }).then((m) => m.default); + + if (content) { + return c.body(content, { + headers: { + "Content-Type": guessMimeType(path), + "Cache-Control": "public, max-age=31536000, immutable", + }, + }); + } + } catch (e) { + console.error("Error serving static file:", e); + return c.text("File not found", 404); + } + } + await next(); + }; +} diff --git a/app/src/adapter/node/connection/NodeSqliteConnection.ts b/app/src/adapter/node/connection/NodeSqliteConnection.ts index c86d7e8c..e215aad3 100644 --- a/app/src/adapter/node/connection/NodeSqliteConnection.ts +++ b/app/src/adapter/node/connection/NodeSqliteConnection.ts @@ -1,4 +1,4 @@ -import { genericSqlite } from "bknd/data"; +import { genericSqlite } from "bknd"; import { DatabaseSync } from "node:sqlite"; export type NodeSqliteConnectionConfig = { diff --git a/app/src/adapter/node/node.adapter.ts b/app/src/adapter/node/node.adapter.ts index 88b7d62e..5a2c0586 100644 --- a/app/src/adapter/node/node.adapter.ts +++ b/app/src/adapter/node/node.adapter.ts @@ -3,9 +3,8 @@ import { serve as honoServe } from "@hono/node-server"; import { serveStatic } from "@hono/node-server/serve-static"; import { registerLocalMediaAdapter } from "adapter/node/storage"; import { type RuntimeBkndConfig, createRuntimeApp, type RuntimeOptions } from "bknd/adapter"; -import { config as $config } from "bknd/core"; -import { $console } from "core/utils"; -import type { App } from "App"; +import { config as $config, type App } from "bknd"; +import { $console } from "bknd/utils"; type NodeEnv = NodeJS.ProcessEnv; export type NodeBkndConfig = RuntimeBkndConfig & { @@ -32,8 +31,8 @@ export async function createApp( registerLocalMediaAdapter(); return await createRuntimeApp( { - ...config, serveStatic: serveStatic({ root }), + ...config, }, // @ts-ignore args ?? { env: process.env }, diff --git a/app/src/adapter/node/storage/StorageLocalAdapter.ts b/app/src/adapter/node/storage/StorageLocalAdapter.ts index 9f624e85..fa3e3361 100644 --- a/app/src/adapter/node/storage/StorageLocalAdapter.ts +++ b/app/src/adapter/node/storage/StorageLocalAdapter.ts @@ -1,8 +1,7 @@ import { readFile, readdir, stat, unlink, writeFile } from "node:fs/promises"; -import { isFile } from "bknd/utils"; -import type { FileBody, FileListObject, FileMeta, FileUploadPayload } from "bknd/media"; -import { StorageAdapter, guessMimeType as guess } from "bknd/media"; -import { parse, s } from "core/object/schema"; +import type { FileBody, FileListObject, FileMeta, FileUploadPayload } from "bknd"; +import { StorageAdapter, guessMimeType } from "bknd"; +import { parse, s, isFile } from "bknd/utils"; export const localAdapterConfig = s.object( { @@ -84,7 +83,7 @@ export class StorageLocalAdapter extends StorageAdapter { async getObject(key: string, headers: Headers): Promise { try { const content = await readFile(`${this.config.path}/${key}`); - const mimeType = guess(key); + const mimeType = guessMimeType(key); return new Response(content, { status: 200, @@ -106,7 +105,7 @@ export class StorageLocalAdapter extends StorageAdapter { async getObjectMeta(key: string): Promise { const stats = await stat(`${this.config.path}/${key}`); return { - type: guess(key) || "application/octet-stream", + type: guessMimeType(key) || "application/octet-stream", size: stats.size, }; } diff --git a/app/src/adapter/sqlite/bun.ts b/app/src/adapter/sqlite/bun.ts index 6d549181..46cd5993 100644 --- a/app/src/adapter/sqlite/bun.ts +++ b/app/src/adapter/sqlite/bun.ts @@ -1,4 +1,4 @@ -import type { Connection } from "bknd/data"; +import type { Connection } from "bknd"; import { bunSqlite } from "../bun/connection/BunSqliteConnection"; export function sqlite(config?: { url: string }): Connection { diff --git a/app/src/adapter/sqlite/edge.ts b/app/src/adapter/sqlite/edge.ts index f5b584f2..6c0f041b 100644 --- a/app/src/adapter/sqlite/edge.ts +++ b/app/src/adapter/sqlite/edge.ts @@ -1,4 +1,4 @@ -import { type Connection, libsql } from "bknd/data"; +import { type Connection, libsql } from "bknd"; export function sqlite(config: { url: string }): Connection { return libsql(config); diff --git a/app/src/adapter/sqlite/node.ts b/app/src/adapter/sqlite/node.ts index f14a8569..cf0982da 100644 --- a/app/src/adapter/sqlite/node.ts +++ b/app/src/adapter/sqlite/node.ts @@ -1,4 +1,4 @@ -import type { Connection } from "bknd/data"; +import type { Connection } from "bknd"; import { nodeSqlite } from "../node/connection/NodeSqliteConnection"; export function sqlite(config?: { url: string }): Connection { diff --git a/app/src/auth/AppAuth.ts b/app/src/auth/AppAuth.ts index 334a5216..6596a2bd 100644 --- a/app/src/auth/AppAuth.ts +++ b/app/src/auth/AppAuth.ts @@ -1,18 +1,26 @@ -import { Authenticator, AuthPermissions, Role, type Strategy } from "auth"; -import type { PasswordStrategy } from "auth/authenticate/strategies"; -import type { DB } from "core"; +import type { DB } from "bknd"; +import * as AuthPermissions from "auth/auth-permissions"; +import type { AuthStrategy } from "auth/authenticate/strategies/Strategy"; +import type { PasswordStrategy } from "auth/authenticate/strategies/PasswordStrategy"; import { $console, secureRandomString, transformObject } from "core/utils"; -import type { Entity, EntityManager } from "data"; +import type { Entity, EntityManager } from "data/entities"; import { em, entity, enumm, type FieldSchema } from "data/prototype"; import { Module } from "modules/Module"; import { AuthController } from "./api/AuthController"; -import { type AppAuthSchema, authConfigSchema, AuthStrategyRegistry } from "./auth-schema"; +import { + type AppAuthSchema, + authConfigSchema, + AuthStrategyRegistry, + buildAuthSchema, +} from "./auth-schema"; import { AppUserPool } from "auth/AppUserPool"; import type { AppEntity } from "core/config"; import { usersFields } from "./auth-entities"; +import { Authenticator } from "./authenticate/Authenticator"; +import { Role } from "./authorize/Role"; export type UserFieldSchema = FieldSchema; -declare module "core" { +declare module "bknd" { interface Users extends AppEntity, UserFieldSchema {} interface DB { users: Users; @@ -89,7 +97,7 @@ export class AppAuth extends Module { this.ctx.guard.registerPermissions(AuthPermissions); } - isStrategyEnabled(strategy: Strategy | string) { + isStrategyEnabled(strategy: AuthStrategy | string) { const name = typeof strategy === "string" ? strategy : strategy.getName(); // for now, password is always active if (name === "password") return true; @@ -106,7 +114,7 @@ export class AppAuth extends Module { } getSchema() { - return authConfigSchema; + return buildAuthSchema(); } get authenticator(): Authenticator { diff --git a/app/src/auth/api/AuthApi.ts b/app/src/auth/api/AuthApi.ts index 91b3c176..cd22ada4 100644 --- a/app/src/auth/api/AuthApi.ts +++ b/app/src/auth/api/AuthApi.ts @@ -1,6 +1,6 @@ import type { AuthActionResponse } from "auth/api/AuthController"; import type { AppAuthSchema } from "auth/auth-schema"; -import type { AuthResponse, SafeUser, Strategy } from "auth/authenticate/Authenticator"; +import type { AuthResponse, SafeUser, AuthStrategy } from "bknd"; import { type BaseModuleApiOptions, ModuleApi } from "modules/ModuleApi"; export type AuthApiOptions = BaseModuleApiOptions & { @@ -39,7 +39,7 @@ export class AuthApi extends ModuleApi { } async actionSchema(strategy: string, action: string) { - return this.get([strategy, "actions", action, "schema.json"]); + return this.get([strategy, "actions", action, "schema.json"]); } async action(strategy: string, action: string, input: any) { diff --git a/app/src/auth/api/AuthController.ts b/app/src/auth/api/AuthController.ts index f7dca82f..b0396352 100644 --- a/app/src/auth/api/AuthController.ts +++ b/app/src/auth/api/AuthController.ts @@ -1,9 +1,11 @@ -import { type AppAuth, AuthPermissions, type SafeUser, type Strategy } from "auth"; -import { transformObject } from "core/utils"; -import { DataPermissions } from "data"; +import type { SafeUser } from "bknd"; +import type { AuthStrategy } from "auth/authenticate/strategies/Strategy"; +import type { AppAuth } from "auth/AppAuth"; +import * as AuthPermissions from "auth/auth-permissions"; +import * as DataPermissions from "data/permissions"; import type { Hono } from "hono"; import { Controller, type ServerEnv } from "modules/Controller"; -import { describeRoute, jsc, s, parse, InvalidSchemaError } from "core/object/schema"; +import { describeRoute, jsc, s, parse, InvalidSchemaError, transformObject } from "bknd/utils"; export type AuthActionResponse = { success: boolean; @@ -30,7 +32,7 @@ export class AuthController extends Controller { return this.em.repo(entity_name as "users"); } - private registerStrategyActions(strategy: Strategy, mainHono: Hono) { + private registerStrategyActions(strategy: AuthStrategy, mainHono: Hono) { if (!this.auth.isStrategyEnabled(strategy)) { return; } diff --git a/app/src/auth/auth-permissions.ts b/app/src/auth/auth-permissions.ts index ed719925..ed57c505 100644 --- a/app/src/auth/auth-permissions.ts +++ b/app/src/auth/auth-permissions.ts @@ -1,4 +1,4 @@ -import { Permission } from "core"; +import { Permission } from "core/security/Permission"; export const createUser = new Permission("auth.user.create"); //export const updateUser = new Permission("auth.user.update"); diff --git a/app/src/auth/auth-schema.ts b/app/src/auth/auth-schema.ts index e7b79dc0..1db739fe 100644 --- a/app/src/auth/auth-schema.ts +++ b/app/src/auth/auth-schema.ts @@ -1,17 +1,17 @@ -import { cookieConfig, jwtConfig, type Strategy } from "auth/authenticate/Authenticator"; -import { objectTransform } from "core/utils"; -import { s } from "core/object/schema"; +import { Registry, type Constructor } from "core/registry/Registry"; +import { cookieConfig, jwtConfig } from "auth/authenticate/Authenticator"; +import { objectTransform, s } from "bknd/utils"; import type { ProvidedOAuthConfig } from "./authenticate/strategies/oauth/OAuthStrategy"; import type { OAuthConfigCustom } from "./authenticate/strategies/oauth/CustomOAuthStrategy"; -import { Registry, type Constructor } from "core"; import { PasswordStrategy } from "./authenticate/strategies/PasswordStrategy"; import { OAuthStrategy } from "./authenticate/strategies/oauth/OAuthStrategy"; import { CustomOAuthStrategy } from "./authenticate/strategies/oauth/CustomOAuthStrategy"; +import type { AuthStrategy } from "auth/authenticate/strategies/Strategy"; export const AuthStrategyRegistry = new Registry<{ - cls: Constructor; + cls: Constructor; schema: s.Schema; -}>((cls: Constructor) => ({ +}>((cls: Constructor) => ({ cls, schema: cls.prototype.getSchema() as s.Schema, })) @@ -19,22 +19,6 @@ export const AuthStrategyRegistry = new Registry<{ .register("oauth", OAuthStrategy) .register("custom_oauth", CustomOAuthStrategy); -export const STRATEGIES = AuthStrategyRegistry.all(); -const strategiesSchemaObject = objectTransform(STRATEGIES, (strategy, name) => { - return s.strictObject( - { - enabled: s.boolean({ default: true }).optional(), - type: s.literal(name), - config: strategy.schema, - }, - { - title: name, - }, - ); -}); - -const strategiesSchema = s.anyOf(Object.values(strategiesSchemaObject)); -export type AppAuthStrategies = s.Static; export type AppAuthOAuthStrategy = ProvidedOAuthConfig; export type AppAuthCustomOAuthStrategy = OAuthConfigCustom; @@ -47,32 +31,49 @@ export const guardRoleSchema = s.strictObject({ implicit_allow: s.boolean().optional(), }); -export const authConfigSchema = s.strictObject( - { - enabled: s.boolean({ default: false }), - basepath: s.string({ default: "/api/auth" }), - entity_name: s.string({ default: "users" }), - allow_register: s.boolean({ default: true }).optional(), - jwt: jwtConfig, - cookie: cookieConfig, - strategies: s.record(strategiesSchema, { - title: "Strategies", - default: { - password: { - type: "password", - enabled: true, - config: { - hashing: "sha256", +export function buildAuthSchema() { + const strategiesSchemaObject = objectTransform(AuthStrategyRegistry.all(), (strategy, name) => { + return s.strictObject( + { + enabled: s.boolean({ default: true }).optional(), + type: s.literal(name), + config: strategy.schema, + }, + { + title: name, + }, + ); + }); + + const strategiesSchema = s.anyOf(Object.values(strategiesSchemaObject)); + return s.strictObject( + { + enabled: s.boolean({ default: false }), + basepath: s.string({ default: "/api/auth" }), + entity_name: s.string({ default: "users" }), + allow_register: s.boolean({ default: true }).optional(), + jwt: jwtConfig, + cookie: cookieConfig, + strategies: s.record(strategiesSchema, { + title: "Strategies", + default: { + password: { + type: "password", + enabled: true, + config: { + hashing: "sha256", + }, }, }, - }, - }), - guard: guardConfigSchema.optional(), - roles: s.record(guardRoleSchema, { default: {} }).optional(), - }, - { title: "Authentication" }, -); + }), + guard: guardConfigSchema.optional(), + roles: s.record(guardRoleSchema, { default: {} }).optional(), + }, + { title: "Authentication" }, + ); +} +export const authConfigSchema = buildAuthSchema(); +export type AppAuthStrategies = s.Static<(typeof authConfigSchema)["properties"]["strategies"]>; export type AppAuthJWTConfig = s.Static; - export type AppAuthSchema = s.Static; diff --git a/app/src/auth/authenticate/Authenticator.ts b/app/src/auth/authenticate/Authenticator.ts index f7a3e401..46dfc047 100644 --- a/app/src/auth/authenticate/Authenticator.ts +++ b/app/src/auth/authenticate/Authenticator.ts @@ -1,14 +1,15 @@ -import { type DB, Exception } from "core"; +import type { DB } from "bknd"; +import { Exception } from "core/errors"; import { addFlashMessage } from "core/server/flash"; -import { runtimeSupports, truncate, $console } from "core/utils"; -import type { Context, Hono } from "hono"; +import type { Context } from "hono"; import { deleteCookie, getSignedCookie, setSignedCookie } from "hono/cookie"; import { sign, verify } from "hono/jwt"; -import type { CookieOptions } from "hono/utils/cookie"; +import { type CookieOptions, serializeSigned } from "hono/utils/cookie"; import type { ServerEnv } from "modules/Controller"; import { pick } from "lodash-es"; import { InvalidConditionsException } from "auth/errors"; -import { s, parse, secret } from "core/object/schema"; +import { s, parse, secret, runtimeSupports, truncate, $console } from "bknd/utils"; +import type { AuthStrategy } from "./strategies/Strategy"; type Input = any; // workaround export type JWTPayload = Parameters[0]; @@ -21,17 +22,6 @@ export type StrategyAction = { }; export type StrategyActions = Partial>; -// @todo: add schema to interface to ensure proper inference -// @todo: add tests (e.g. invalid strategy_value) -export interface Strategy { - getController: (auth: Authenticator) => Hono; - getType: () => string; - getMode: () => "form" | "external"; - getName: () => string; - toJSON: (secrets?: boolean) => any; - getActions?: () => StrategyActions; -} - export type User = DB["users"]; export type ProfileExchange = { @@ -58,6 +48,7 @@ export const cookieConfig = s secure: s.boolean({ default: true }), httpOnly: s.boolean({ default: true }), expires: s.number({ default: defaultCookieExpires }), // seconds + partitioned: s.boolean({ default: false }), renew: s.boolean({ default: true }), pathSuccess: s.string({ default: "/" }), pathLoggedOut: s.string({ default: "/" }), @@ -97,7 +88,7 @@ export type AuthResolveOptions = { }; export type AuthUserResolver = ( action: AuthAction, - strategy: Strategy, + strategy: AuthStrategy, profile: ProfileExchange, opts?: AuthResolveOptions, ) => Promise; @@ -107,7 +98,9 @@ type AuthClaims = SafeUser & { exp?: number; }; -export class Authenticator = Record> { +export class Authenticator< + Strategies extends Record = Record, +> { private readonly config: AuthConfig; constructor( @@ -120,7 +113,7 @@ export class Authenticator = Record< async resolveLogin( c: Context, - strategy: Strategy, + strategy: AuthStrategy, profile: Partial, verify: (user: User) => Promise, opts?: AuthResolveOptions, @@ -158,7 +151,7 @@ export class Authenticator = Record< async resolveRegister( c: Context, - strategy: Strategy, + strategy: AuthStrategy, profile: CreateUser, verify: (user: User) => Promise, opts?: AuthResolveOptions, @@ -217,7 +210,7 @@ export class Authenticator = Record< await addFlashMessage(c, String(error), "error"); - const referer = this.getSafeUrl(c, opts?.redirect ?? c.req.header("Referer") ?? "/"); + const referer = this.getSafeUrl(c, c.req.header("Referer") ?? "/"); return c.redirect(referer); } @@ -227,7 +220,7 @@ export class Authenticator = Record< strategy< StrategyName extends keyof Strategies, - Strat extends Strategy = Strategies[StrategyName], + Strat extends AuthStrategy = Strategies[StrategyName], >(strategy: StrategyName): Strat { try { return this.strategies[strategy] as unknown as Strat; @@ -334,6 +327,11 @@ export class Authenticator = Record< await setSignedCookie(c, "auth", token, secret, this.cookieOptions); } + async unsafeGetAuthCookie(token: string): Promise { + // this works for as long as cookieOptions.prefix is not set + return serializeSigned("auth", token, this.config.jwt.secret, this.cookieOptions); + } + private deleteAuthCookie(c: Context) { $console.debug("deleting auth cookie"); deleteCookie(c, "auth", this.cookieOptions); diff --git a/app/src/auth/authenticate/strategies/PasswordStrategy.ts b/app/src/auth/authenticate/strategies/PasswordStrategy.ts index d510431f..1ee6d36c 100644 --- a/app/src/auth/authenticate/strategies/PasswordStrategy.ts +++ b/app/src/auth/authenticate/strategies/PasswordStrategy.ts @@ -1,9 +1,11 @@ -import { type Authenticator, InvalidCredentialsException, type User } from "auth"; +import type { User } from "bknd"; +import type { Authenticator } from "auth/authenticate/Authenticator"; +import { InvalidCredentialsException } from "auth/errors"; import { hash, $console } from "core/utils"; import { Hono } from "hono"; import { compare as bcryptCompare, genSalt as bcryptGenSalt, hash as bcryptHash } from "bcryptjs"; -import { Strategy } from "./Strategy"; -import { s, parse, jsc } from "core/object/schema"; +import { AuthStrategy } from "./Strategy"; +import { s, parse, jsc } from "bknd/utils"; const schema = s .object({ @@ -14,7 +16,7 @@ const schema = s export type PasswordStrategyOptions = s.Static; -export class PasswordStrategy extends Strategy { +export class PasswordStrategy extends AuthStrategy { constructor(config: Partial = {}) { super(config as any, "password", "password", "form"); @@ -33,7 +35,7 @@ export class PasswordStrategy extends Strategy { private getPayloadSchema() { return s.object({ email: s.string({ - pattern: /^[\w-\.\+_]+@([\w-]+\.)+[\w-]{2,4}$/, + format: "email", }), password: s.string({ minLength: 8, // @todo: this should be configurable diff --git a/app/src/auth/authenticate/strategies/Strategy.ts b/app/src/auth/authenticate/strategies/Strategy.ts index 41e0770d..bb72d856 100644 --- a/app/src/auth/authenticate/strategies/Strategy.ts +++ b/app/src/auth/authenticate/strategies/Strategy.ts @@ -5,11 +5,11 @@ import type { StrategyActions, } from "../Authenticator"; import type { Hono } from "hono"; -import { type s, parse } from "core/object/schema"; +import { type s, parse } from "bknd/utils"; export type StrategyMode = "form" | "external"; -export abstract class Strategy { +export abstract class AuthStrategy { protected actions: StrategyActions = {}; constructor( diff --git a/app/src/auth/authenticate/strategies/oauth/CustomOAuthStrategy.ts b/app/src/auth/authenticate/strategies/oauth/CustomOAuthStrategy.ts index 73f6bc20..7fca28b3 100644 --- a/app/src/auth/authenticate/strategies/oauth/CustomOAuthStrategy.ts +++ b/app/src/auth/authenticate/strategies/oauth/CustomOAuthStrategy.ts @@ -1,6 +1,6 @@ import type * as oauth from "oauth4webapi"; import { OAuthStrategy } from "./OAuthStrategy"; -import { s } from "core/object/schema"; +import { s } from "bknd/utils"; type SupportedTypes = "oauth2" | "oidc"; diff --git a/app/src/auth/authenticate/strategies/oauth/OAuthStrategy.ts b/app/src/auth/authenticate/strategies/oauth/OAuthStrategy.ts index b4d770db..ad877df7 100644 --- a/app/src/auth/authenticate/strategies/oauth/OAuthStrategy.ts +++ b/app/src/auth/authenticate/strategies/oauth/OAuthStrategy.ts @@ -1,12 +1,12 @@ -import type { AuthAction, Authenticator } from "auth"; -import { Exception, isDebug } from "core"; -import { filterKeys } from "core/utils"; +import type { Authenticator, AuthAction } from "auth/authenticate/Authenticator"; import { type Context, Hono } from "hono"; import { getSignedCookie, setSignedCookie } from "hono/cookie"; import * as oauth from "oauth4webapi"; import * as issuers from "./issuers"; -import { Strategy } from "auth/authenticate/strategies/Strategy"; -import { s } from "core/object/schema"; +import { s, filterKeys } from "bknd/utils"; +import { Exception } from "core/errors"; +import { isDebug } from "core/env"; +import { AuthStrategy } from "../Strategy"; type ConfiguredIssuers = keyof typeof issuers; type SupportedTypes = "oauth2" | "oidc"; @@ -70,7 +70,7 @@ export class OAuthCallbackException extends Exception { } } -export class OAuthStrategy extends Strategy { +export class OAuthStrategy extends AuthStrategy { constructor(config: ProvidedOAuthConfig) { super(config, "oauth", config.name, "external"); } diff --git a/app/src/auth/authorize/Guard.ts b/app/src/auth/authorize/Guard.ts index 81280db2..09d36fb1 100644 --- a/app/src/auth/authorize/Guard.ts +++ b/app/src/auth/authorize/Guard.ts @@ -1,5 +1,6 @@ -import { Exception, Permission } from "core"; +import { Exception } from "core/errors"; import { $console, objectTransform } from "core/utils"; +import { Permission } from "core/security/Permission"; import type { Context } from "hono"; import type { ServerEnv } from "modules/Controller"; import { Role } from "./Role"; diff --git a/app/src/auth/authorize/Role.ts b/app/src/auth/authorize/Role.ts index b5b09b28..54efaf12 100644 --- a/app/src/auth/authorize/Role.ts +++ b/app/src/auth/authorize/Role.ts @@ -1,4 +1,4 @@ -import { Permission } from "core"; +import { Permission } from "core/security/Permission"; export class RolePermission { constructor( diff --git a/app/src/auth/errors.ts b/app/src/auth/errors.ts index 7b725af8..5333b6a7 100644 --- a/app/src/auth/errors.ts +++ b/app/src/auth/errors.ts @@ -1,5 +1,6 @@ -import { Exception, isDebug } from "core"; -import { HttpStatus } from "core/utils"; +import { Exception } from "core/errors"; +import { isDebug } from "core/env"; +import { HttpStatus } from "bknd/utils"; export class AuthException extends Exception { getSafeErrorAndCode() { diff --git a/app/src/auth/index.ts b/app/src/auth/index.ts deleted file mode 100644 index 513eb9ac..00000000 --- a/app/src/auth/index.ts +++ /dev/null @@ -1,22 +0,0 @@ -export { UserExistsException, UserNotFoundException, InvalidCredentialsException } from "./errors"; -export { - type ProfileExchange, - type Strategy, - type User, - type SafeUser, - type CreateUser, - type AuthResponse, - type UserPool, - type AuthAction, - type AuthUserResolver, - Authenticator, - authenticatorConfig, - jwtConfig, -} from "./authenticate/Authenticator"; - -export { AppAuth, type UserFieldSchema } from "./AppAuth"; - -export { Guard, type GuardUserContext, type GuardConfig } from "./authorize/Guard"; -export { Role } from "./authorize/Role"; - -export * as AuthPermissions from "./auth-permissions"; diff --git a/app/src/auth/middlewares.ts b/app/src/auth/middlewares.ts index b58b540a..702023b9 100644 --- a/app/src/auth/middlewares.ts +++ b/app/src/auth/middlewares.ts @@ -1,5 +1,5 @@ -import type { Permission } from "core"; -import { $console, patternMatch } from "core/utils"; +import type { Permission } from "core/security/Permission"; +import { $console, patternMatch } from "bknd/utils"; import type { Context } from "hono"; import { createMiddleware } from "hono/factory"; import type { ServerEnv } from "modules/Controller"; diff --git a/app/src/cli/commands/create/create.ts b/app/src/cli/commands/create/create.ts index e3d7fc48..217b07dc 100644 --- a/app/src/cli/commands/create/create.ts +++ b/app/src/cli/commands/create/create.ts @@ -5,7 +5,7 @@ import type { CliCommand } from "cli/types"; import { typewriter, wait } from "cli/utils/cli"; import { execAsync, getVersion } from "cli/utils/sys"; import { Option } from "commander"; -import { env } from "core"; +import { env } from "bknd"; import color from "picocolors"; import { overridePackageJson, updateBkndPackages } from "./npm"; import { type Template, templates, type TemplateSetupCtx } from "./templates"; diff --git a/app/src/cli/commands/run/run.ts b/app/src/cli/commands/run/run.ts index 0830bc69..24c14b58 100644 --- a/app/src/cli/commands/run/run.ts +++ b/app/src/cli/commands/run/run.ts @@ -1,9 +1,8 @@ import type { Config } from "@libsql/client/node"; -import type { App, CreateAppConfig } from "App"; import { StorageLocalAdapter } from "adapter/node/storage"; import type { CliBkndConfig, CliCommand } from "cli/types"; import { Option } from "commander"; -import { config } from "core"; +import { config, type App, type CreateAppConfig } from "bknd"; import dotenv from "dotenv"; import { registries } from "modules/registries"; import c from "picocolors"; @@ -16,8 +15,8 @@ import { serveStatic, startServer, } from "./platform"; -import { createRuntimeApp, makeConfig } from "adapter"; -import { colorizeConsole, isBun } from "core/utils"; +import { createRuntimeApp, makeConfig } from "bknd/adapter"; +import { colorizeConsole, isBun } from "bknd/utils"; const env_files = [".env", ".dev.vars"]; dotenv.config({ diff --git a/app/src/cli/utils/telemetry.ts b/app/src/cli/utils/telemetry.ts index 6673e0e4..9c9fec13 100644 --- a/app/src/cli/utils/telemetry.ts +++ b/app/src/cli/utils/telemetry.ts @@ -1,7 +1,7 @@ import { PostHog } from "posthog-js-lite"; import { getVersion } from "cli/utils/sys"; -import { env, isDebug } from "core"; -import { $console } from "core/utils"; +import { env, isDebug } from "bknd"; +import { $console } from "bknd/utils"; type Properties = { [p: string]: any }; diff --git a/app/src/core/drivers/email/index.ts b/app/src/core/drivers/email/index.ts index 494276df..94f9d388 100644 --- a/app/src/core/drivers/email/index.ts +++ b/app/src/core/drivers/email/index.ts @@ -6,23 +6,3 @@ export interface IEmailDriver { options?: Options, ): Promise; } - -import type { BkndConfig } from "bknd"; -import { resendEmail, memoryCache } from "bknd/core"; - -export default { - onBuilt: async (app) => { - app.server.get("/send-email", async (c) => { - if (await app.drivers?.email?.send("test@test.com", "Test", "Test")) { - return c.text("success"); - } - return c.text("failed"); - }); - }, - options: { - drivers: { - email: resendEmail({ apiKey: "..." }), - cache: memoryCache(), - }, - }, -} as const satisfies BkndConfig; diff --git a/app/src/core/index.ts b/app/src/core/index.ts deleted file mode 100644 index 8ad4507b..00000000 --- a/app/src/core/index.ts +++ /dev/null @@ -1,47 +0,0 @@ -import type { Hono, MiddlewareHandler } from "hono"; - -export { Exception, BkndError } from "./errors"; -export { isDebug, env } from "./env"; -export { type PrimaryFieldType, config, type DB, type AppEntity } from "./config"; -export { AwsClient } from "./clients/aws/AwsClient"; -export { - SimpleRenderer, - type TemplateObject, - type TemplateTypes, - type SimpleRendererOptions, -} from "./template/SimpleRenderer"; -export { SchemaObject } from "./object/SchemaObject"; -export { DebugLogger } from "./utils/DebugLogger"; -export { Permission } from "./security/Permission"; -export { - exp, - makeValidator, - type FilterQuery, - type Primitive, - isPrimitive, - type TExpression, - type BooleanLike, - isBooleanLike, -} from "./object/query/query"; -export { Registry, type Constructor, type ClassThatImplements } from "./registry/Registry"; -export { getFlashMessage } from "./server/flash"; -/* export { - s, - parse, - jsc, - describeRoute, - schemaToSpec, - openAPISpecs, - type ParseOptions, - InvalidSchemaError, -} from "./object/schema"; */ - -export * from "./drivers"; -export * from "./events"; - -// compatibility -export type Middleware = MiddlewareHandler; -export interface ClassController { - getController: () => Hono; - getMiddleware?: MiddlewareHandler; -} diff --git a/app/src/core/object/SchemaObject.ts b/app/src/core/object/SchemaObject.ts index 3117758c..e16e51aa 100644 --- a/app/src/core/object/SchemaObject.ts +++ b/app/src/core/object/SchemaObject.ts @@ -1,6 +1,5 @@ import { get, has, omit, set } from "lodash-es"; -import { getFullPathKeys, mergeObjectWith } from "../utils"; -import { type s, parse, stripMark } from "core/object/schema"; +import { type s, parse, stripMark, getFullPathKeys, mergeObjectWith, deepFreeze } from "bknd/utils"; export type SchemaObjectOptions = { onUpdate?: (config: s.Static) => void | Promise; @@ -26,14 +25,16 @@ export class SchemaObject { initial?: Partial>, private options?: SchemaObjectOptions, ) { - this._default = _schema.template({}, { withOptional: true }) as any; - this._value = parse(_schema, structuredClone(initial ?? {}), { - withDefaults: true, - withExtendedDefaults: true, - forceParse: this.isForceParse(), - skipMark: this.isForceParse(), - }); - this._config = Object.freeze(this._value); + this._default = deepFreeze(_schema.template({}, { withOptional: true }) as any); + this._value = deepFreeze( + parse(_schema, structuredClone(initial ?? {}), { + withDefaults: true, + withExtendedDefaults: true, + forceParse: this.isForceParse(), + skipMark: this.isForceParse(), + }), + ); + this._config = deepFreeze(this._value); } protected isForceParse(): boolean { @@ -76,8 +77,8 @@ export class SchemaObject { // regardless of "noEmit" – this should always be triggered const updatedConfig = await this.onBeforeUpdate(this._config, valid); - this._value = updatedConfig; - this._config = Object.freeze(updatedConfig); + this._value = deepFreeze(updatedConfig); + this._config = deepFreeze(updatedConfig); if (noEmit !== true) { await this.options?.onUpdate?.(this._config); diff --git a/app/src/core/object/query/query.ts b/app/src/core/object/query/query.ts index 27180ee5..e90921d2 100644 --- a/app/src/core/object/query/query.ts +++ b/app/src/core/object/query/query.ts @@ -1,4 +1,4 @@ -import type { PrimaryFieldType } from "core"; +import type { PrimaryFieldType } from "core/config"; export type Primitive = PrimaryFieldType | string | number | boolean; export function isPrimitive(value: any): value is Primitive { diff --git a/app/src/core/template/SimpleRenderer.spec.ts b/app/src/core/template/SimpleRenderer.spec.ts index 6f922f36..66eb423e 100644 --- a/app/src/core/template/SimpleRenderer.spec.ts +++ b/app/src/core/template/SimpleRenderer.spec.ts @@ -1,5 +1,5 @@ import { describe, expect, test } from "bun:test"; -import { SimpleRenderer } from "core"; +import { SimpleRenderer } from "./SimpleRenderer"; describe(SimpleRenderer, () => { const renderer = new SimpleRenderer( diff --git a/app/src/core/utils/console.ts b/app/src/core/utils/console.ts index 756d97b9..b07fa2cd 100644 --- a/app/src/core/utils/console.ts +++ b/app/src/core/utils/console.ts @@ -1,6 +1,6 @@ -import { datetimeStringLocal } from "core/utils"; +import { datetimeStringLocal } from "./dates"; import colors from "picocolors"; -import { env } from "core"; +import { env } from "core/env"; function hasColors() { try { diff --git a/app/src/core/utils/index.ts b/app/src/core/utils/index.ts index e60d272e..36928c5b 100644 --- a/app/src/core/utils/index.ts +++ b/app/src/core/utils/index.ts @@ -13,3 +13,18 @@ export * from "./uuid"; export * from "./test"; export * from "./runtime"; export * from "./numbers"; +export { + s, + stripMark, + mark, + stringIdentifier, + SecretSchema, + secret, + parse, + jsc, + describeRoute, + schemaToSpec, + openAPISpecs, + type ParseOptions, + InvalidSchemaError, +} from "./schema"; diff --git a/app/src/core/utils/objects.ts b/app/src/core/utils/objects.ts index a14f1c3c..2bf1e60c 100644 --- a/app/src/core/utils/objects.ts +++ b/app/src/core/utils/objects.ts @@ -94,16 +94,14 @@ export function transformObject, U>( object: T, transform: (value: T[keyof T], key: keyof T) => U | undefined, ): { [K in keyof T]: U } { - return Object.entries(object).reduce( - (acc, [key, value]) => { - const t = transform(value, key as keyof T); - if (typeof t !== "undefined") { - acc[key as keyof T] = t; - } - return acc; - }, - {} as { [K in keyof T]: U }, - ); + const result = {} as { [K in keyof T]: U }; + for (const [key, value] of Object.entries(object) as [keyof T, T[keyof T]][]) { + const t = transform(value, key); + if (typeof t !== "undefined") { + result[key] = t; + } + } + return result; } export const objectTransform = transformObject; @@ -419,3 +417,21 @@ export function pick(obj: T, keys: K[]): Pi {} as Pick, ); } + +export function deepFreeze(object: T): T { + if (Object.isFrozen(object)) return object; + + // Retrieve the property names defined on object + const propNames = Reflect.ownKeys(object); + + // Freeze properties before freezing self + for (const name of propNames) { + const value = object[name]; + + if ((value && typeof value === "object") || typeof value === "function") { + deepFreeze(value); + } + } + + return Object.freeze(object); +} diff --git a/app/src/core/object/schema/index.ts b/app/src/core/utils/schema/index.ts similarity index 90% rename from app/src/core/object/schema/index.ts rename to app/src/core/utils/schema/index.ts index 72f36e09..03827008 100644 --- a/app/src/core/object/schema/index.ts +++ b/app/src/core/utils/schema/index.ts @@ -3,7 +3,7 @@ import * as s from "jsonv-ts"; export { validator as jsc, type Options } from "jsonv-ts/hono"; export { describeRoute, schemaToSpec, openAPISpecs } from "jsonv-ts/hono"; -export { secret } from "./secret"; +export { secret, SecretSchema } from "./secret"; export { s }; @@ -42,6 +42,7 @@ export type ParseOptions = { withDefaults?: boolean; withExtendedDefaults?: boolean; coerce?: boolean; + coerceDropUnknown?: boolean; clone?: boolean; skipMark?: boolean; // @todo: do something with this forceParse?: boolean; // @todo: do something with this @@ -59,7 +60,10 @@ export function parse : s.Static { const schema = (opts?.clone ? cloneSchema(_schema as any) : _schema) as s.Schema; - let value = opts?.coerce !== false ? schema.coerce(v) : v; + let value = + opts?.coerce !== false + ? schema.coerce(v, { dropUnknown: opts?.coerceDropUnknown ?? false }) + : v; if (opts?.withDefaults !== false) { value = schema.template(value, { withOptional: true, diff --git a/app/src/core/object/schema/secret.ts b/app/src/core/utils/schema/secret.ts similarity index 100% rename from app/src/core/object/schema/secret.ts rename to app/src/core/utils/schema/secret.ts diff --git a/app/src/data/AppData.ts b/app/src/data/AppData.ts index 1f0b7b34..0b4e4641 100644 --- a/app/src/data/AppData.ts +++ b/app/src/data/AppData.ts @@ -1,15 +1,12 @@ import { transformObject } from "core/utils"; -import { - DataPermissions, - type Entity, - EntityIndex, - type EntityManager, - constructEntity, - constructRelation, -} from "data"; + import { Module } from "modules/Module"; import { DataController } from "./api/DataController"; import { type AppDataConfig, dataConfigSchema } from "./data-schema"; +import { constructEntity, constructRelation } from "./schema/constructor"; +import type { Entity, EntityManager } from "data/entities"; +import { EntityIndex } from "data/fields"; +import * as DataPermissions from "data/permissions"; export class AppData extends Module { override async build() { diff --git a/app/src/data/api/DataApi.ts b/app/src/data/api/DataApi.ts index bd670e86..b4deb5d9 100644 --- a/app/src/data/api/DataApi.ts +++ b/app/src/data/api/DataApi.ts @@ -1,8 +1,9 @@ -import type { DB } from "core"; -import type { EntityData, RepoQueryIn, RepositoryResultJSON } from "data"; +import type { DB, EntityData, RepoQueryIn } from "bknd"; + import type { Insertable, Selectable, Updateable } from "kysely"; import { type BaseModuleApiOptions, ModuleApi, type PrimaryFieldType } from "modules"; import type { FetchPromise, ResponseObject } from "modules/ModuleApi"; +import type { RepositoryResultJSON } from "data/entities/query/RepositoryResult"; export type DataApiOptions = BaseModuleApiOptions & { queryLengthLimit: number; diff --git a/app/src/data/api/DataController.ts b/app/src/data/api/DataController.ts index a8c5e319..b468a080 100644 --- a/app/src/data/api/DataController.ts +++ b/app/src/data/api/DataController.ts @@ -1,17 +1,12 @@ -import { - DataPermissions, - type EntityData, - type EntityManager, - type RepoQuery, - repoQuery, -} from "data"; import type { Handler } from "hono/types"; import type { ModuleBuildContext } from "modules"; import { Controller } from "modules/Controller"; -import { jsc, s, describeRoute, schemaToSpec } from "core/object/schema"; +import { jsc, s, describeRoute, schemaToSpec, omitKeys } from "bknd/utils"; import * as SystemPermissions from "modules/permissions"; import type { AppDataConfig } from "../data-schema"; -import { omitKeys } from "core/utils"; +import type { EntityManager, EntityData } from "data/entities"; +import * as DataPermissions from "data/permissions"; +import { repoQuery, type RepoQuery } from "data/server/query"; export class DataController extends Controller { constructor( @@ -206,7 +201,7 @@ export class DataController extends Controller { const entitiesEnum = this.getEntitiesEnum(this.em); // @todo: make dynamic based on entity - const idType = s.anyOf([s.number(), s.string()], { coerce: (v) => v as any }); + const idType = s.anyOf([s.number(), s.string()], { coerce: (v) => v as number | string }); /** * Function endpoints @@ -387,7 +382,7 @@ export class DataController extends Controller { if (!this.entityExists(entity)) { return this.notFound(c); } - const options = (await c.req.json()) as RepoQuery; + const options = c.req.valid("json") as RepoQuery; const result = await this.em.repository(entity).findMany(options); return c.json(result, { status: result.data ? 200 : 404 }); @@ -397,7 +392,7 @@ export class DataController extends Controller { /** * Mutation endpoints */ - // insert one + // insert one or many hono.post( "/:entity", describeRoute({ diff --git a/app/src/data/connection/Connection.ts b/app/src/data/connection/Connection.ts index 5b39d6d9..7db92274 100644 --- a/app/src/data/connection/Connection.ts +++ b/app/src/data/connection/Connection.ts @@ -18,7 +18,8 @@ import { sql, } from "kysely"; import type { BaseIntrospector, BaseIntrospectorConfig } from "./BaseIntrospector"; -import type { Constructor, DB } from "core"; +import type { DB } from "bknd"; +import type { Constructor } from "core/registry/Registry"; import { KyselyPluginRunner } from "data/plugins/KyselyPluginRunner"; import type { Field } from "data/fields/Field"; diff --git a/app/src/data/connection/sqlite/SqliteConnection.ts b/app/src/data/connection/sqlite/SqliteConnection.ts index 523a8244..afecc383 100644 --- a/app/src/data/connection/sqlite/SqliteConnection.ts +++ b/app/src/data/connection/sqlite/SqliteConnection.ts @@ -8,7 +8,7 @@ import { } from "kysely"; import { jsonArrayFrom, jsonBuildObject, jsonObjectFrom } from "kysely/helpers/sqlite"; import { Connection, type DbFunctions, type FieldSpec, type SchemaResponse } from "../Connection"; -import type { Constructor } from "core"; +import type { Constructor } from "core/registry/Registry"; import { customIntrospector } from "../Connection"; import { SqliteIntrospector } from "./SqliteIntrospector"; import type { Field } from "data/fields/Field"; diff --git a/app/src/data/data-schema.ts b/app/src/data/data-schema.ts index dab2cc63..7b5c0d8d 100644 --- a/app/src/data/data-schema.ts +++ b/app/src/data/data-schema.ts @@ -4,7 +4,7 @@ import { FieldClassMap } from "data/fields"; import { RelationClassMap, RelationFieldClassMap } from "data/relations"; import { entityConfigSchema, entityTypes } from "data/entities"; import { primaryFieldTypes } from "./fields"; -import { s } from "core/object/schema"; +import { s } from "bknd/utils"; export const FIELDS = { ...FieldClassMap, @@ -18,6 +18,7 @@ export const RELATIONS = RelationClassMap; export const fieldsSchemaObject = objectTransform(FIELDS, (field, name) => { return s.strictObject( { + name: s.string().optional(), // @todo: verify, old schema wasn't strict (req in UI) type: s.literal(name), config: field.schema.optional(), }, @@ -32,9 +33,10 @@ export type TAppDataField = s.Static; export type TAppDataEntityFields = s.Static; export const entitiesSchema = s.strictObject({ - type: s.string({ enum: entityTypes, default: "regular", readOnly: true }), - config: entityConfigSchema.optional(), - fields: entityFields.optional(), + name: s.string().optional(), // @todo: verify, old schema wasn't strict (req in UI) + type: s.string({ enum: entityTypes, default: "regular" }), + config: entityConfigSchema, + fields: entityFields, }); export type TAppDataEntity = s.Static; diff --git a/app/src/data/entities/Entity.ts b/app/src/data/entities/Entity.ts index 0222b93b..10612b58 100644 --- a/app/src/data/entities/Entity.ts +++ b/app/src/data/entities/Entity.ts @@ -1,5 +1,5 @@ -import { config } from "core"; -import { snakeToPascalWithSpaces, transformObject, $console } from "core/utils"; +import { config } from "core/config"; +import { snakeToPascalWithSpaces, transformObject, $console, s, parse } from "bknd/utils"; import { type Field, PrimaryField, @@ -7,7 +7,6 @@ import { type TActionContext, type TRenderContext, } from "../fields"; -import { s, parse } from "core/object/schema"; // @todo: entity must be migrated to typebox export const entityConfigSchema = s diff --git a/app/src/data/entities/EntityManager.ts b/app/src/data/entities/EntityManager.ts index 654c77d6..544c8ad6 100644 --- a/app/src/data/entities/EntityManager.ts +++ b/app/src/data/entities/EntityManager.ts @@ -1,5 +1,5 @@ -import type { DB as DefaultDB } from "core"; -import { $console } from "core/utils"; +import type { DB as DefaultDB } from "bknd"; +import { $console } from "bknd/utils"; import { EventManager } from "core/events"; import { sql } from "kysely"; import { Connection } from "../connection/Connection"; @@ -67,6 +67,13 @@ export class EntityManager { return new EntityManager(this._entities, this.connection, this._relations, this._indices); } + clear(): this { + this._entities = []; + this._relations = []; + this._indices = []; + return this; + } + get entities(): Entity[] { return this._entities; } diff --git a/app/src/data/entities/EntityTypescript.ts b/app/src/data/entities/EntityTypescript.ts index 197c22df..79968fc7 100644 --- a/app/src/data/entities/EntityTypescript.ts +++ b/app/src/data/entities/EntityTypescript.ts @@ -1,4 +1,5 @@ -import type { Entity, EntityManager, EntityRelation, TEntityType } from "data"; +import type { Entity, EntityManager, TEntityType } from "data/entities"; +import type { EntityRelation } from "data/relations"; import { autoFormatString } from "core/utils"; import { usersFields } from "auth/auth-entities"; import { mediaFields } from "media/media-entities"; diff --git a/app/src/data/entities/Result.ts b/app/src/data/entities/Result.ts index a37cbd77..0570aa6c 100644 --- a/app/src/data/entities/Result.ts +++ b/app/src/data/entities/Result.ts @@ -1,4 +1,4 @@ -import { isDebug } from "core"; +import { isDebug } from "core/env"; import { pick } from "core/utils"; import type { Connection } from "data/connection"; import type { diff --git a/app/src/data/entities/mutation/Mutator.ts b/app/src/data/entities/mutation/Mutator.ts index 2e01cfe1..84389c3d 100644 --- a/app/src/data/entities/mutation/Mutator.ts +++ b/app/src/data/entities/mutation/Mutator.ts @@ -1,7 +1,7 @@ -import type { DB as DefaultDB, PrimaryFieldType } from "core"; +import type { DB as DefaultDB, PrimaryFieldType } from "bknd"; import { type EmitsEvents, EventManager } from "core/events"; import type { DeleteQueryBuilder, InsertQueryBuilder, UpdateQueryBuilder } from "kysely"; -import type { TActionContext } from "../.."; +import type { TActionContext } from "data/fields"; import { WhereBuilder } from "../query/WhereBuilder"; import type { Entity, EntityData, EntityManager } from "../../entities"; import { InvalidSearchParamsException } from "../../errors"; @@ -9,7 +9,6 @@ import { MutatorEvents } from "../../events"; import { RelationMutator } from "../../relations"; import type { RepoQuery } from "../../server/query"; import { MutatorResult, type MutatorResultOptions } from "./MutatorResult"; -import { transformObject } from "core/utils"; type MutatorQB = | InsertQueryBuilder diff --git a/app/src/data/entities/mutation/MutatorResult.ts b/app/src/data/entities/mutation/MutatorResult.ts index a0fe3075..551bd61f 100644 --- a/app/src/data/entities/mutation/MutatorResult.ts +++ b/app/src/data/entities/mutation/MutatorResult.ts @@ -2,7 +2,7 @@ import { $console } from "core/utils"; import type { Entity, EntityData } from "../Entity"; import type { EntityManager } from "../EntityManager"; import { Result, type ResultJSON, type ResultOptions } from "../Result"; -import { isDebug } from "core"; +import { isDebug } from "core/env"; export type MutatorResultOptions = ResultOptions & { silent?: boolean; diff --git a/app/src/data/entities/query/Repository.ts b/app/src/data/entities/query/Repository.ts index 8c2e2ca5..5f85d808 100644 --- a/app/src/data/entities/query/Repository.ts +++ b/app/src/data/entities/query/Repository.ts @@ -1,4 +1,4 @@ -import type { DB as DefaultDB, PrimaryFieldType } from "core"; +import type { DB as DefaultDB, PrimaryFieldType } from "bknd"; import { $console } from "core/utils"; import { type EmitsEvents, EventManager } from "core/events"; import { type SelectQueryBuilder, sql } from "kysely"; @@ -14,7 +14,6 @@ import { } from "../index"; import { JoinBuilder } from "./JoinBuilder"; import { RepositoryResult, type RepositoryResultOptions } from "./RepositoryResult"; -import type { ResultOptions } from "../Result"; export type RepositoryQB = SelectQueryBuilder; diff --git a/app/src/data/entities/query/WhereBuilder.ts b/app/src/data/entities/query/WhereBuilder.ts index 90d7b365..26bf0417 100644 --- a/app/src/data/entities/query/WhereBuilder.ts +++ b/app/src/data/entities/query/WhereBuilder.ts @@ -6,7 +6,7 @@ import { isBooleanLike, isPrimitive, makeValidator, -} from "core"; +} from "core/object/query/query"; import type { DeleteQueryBuilder, ExpressionBuilder, diff --git a/app/src/data/entities/query/WithBuilder.ts b/app/src/data/entities/query/WithBuilder.ts index 84eebb4b..3f6dde3e 100644 --- a/app/src/data/entities/query/WithBuilder.ts +++ b/app/src/data/entities/query/WithBuilder.ts @@ -1,7 +1,9 @@ import { isObject } from "core/utils"; -import type { KyselyJsonFrom, RepoQuery } from "data"; + +import type { KyselyJsonFrom } from "data/relations/EntityRelation"; +import type { RepoQuery } from "data/server/query"; import { InvalidSearchParamsException } from "data/errors"; -import type { Entity, EntityManager, RepositoryQB } from "../../entities"; +import type { Entity, EntityManager, RepositoryQB } from "data/entities"; export class WithBuilder { static addClause( diff --git a/app/src/data/errors.ts b/app/src/data/errors.ts index 35830892..ca530c66 100644 --- a/app/src/data/errors.ts +++ b/app/src/data/errors.ts @@ -1,6 +1,5 @@ -import { Exception } from "core"; -import { HttpStatus } from "core/utils"; -import type { InvalidSchemaError } from "core/object/schema"; +import { Exception } from "core/errors"; +import { type InvalidSchemaError, HttpStatus } from "bknd/utils"; import type { Entity } from "./entities"; import type { Field } from "./fields"; diff --git a/app/src/data/events/index.ts b/app/src/data/events/index.ts index 246a39d3..beb44938 100644 --- a/app/src/data/events/index.ts +++ b/app/src/data/events/index.ts @@ -1,5 +1,5 @@ -import type { PrimaryFieldType } from "core"; -import { $console } from "core/utils"; +import type { PrimaryFieldType } from "bknd"; +import { $console } from "bknd/utils"; import { Event, InvalidEventReturn } from "core/events"; import type { Entity, EntityData } from "../entities"; import type { RepoQuery } from "data/server/query"; diff --git a/app/src/data/fields/BooleanField.ts b/app/src/data/fields/BooleanField.ts index ec14380f..1655a895 100644 --- a/app/src/data/fields/BooleanField.ts +++ b/app/src/data/fields/BooleanField.ts @@ -1,8 +1,8 @@ import { omitKeys } from "core/utils"; -import type { EntityManager } from "data"; +import type { EntityManager } from "data/entities"; import { TransformPersistFailedException } from "../errors"; import { Field, type TActionContext, type TRenderContext, baseFieldConfigSchema } from "./Field"; -import { s } from "core/object/schema"; +import { s } from "bknd/utils"; export const booleanFieldConfigSchema = s .strictObject({ diff --git a/app/src/data/fields/DateField.ts b/app/src/data/fields/DateField.ts index a8de5c7a..06249866 100644 --- a/app/src/data/fields/DateField.ts +++ b/app/src/data/fields/DateField.ts @@ -3,7 +3,7 @@ import type { EntityManager } from "../entities"; import { Field, type TActionContext, type TRenderContext, baseFieldConfigSchema } from "./Field"; import { $console } from "core/utils"; import type { TFieldTSType } from "data/entities/EntityTypescript"; -import { s } from "core/object/schema"; +import { s } from "bknd/utils"; export const dateFieldConfigSchema = s .strictObject({ diff --git a/app/src/data/fields/EnumField.ts b/app/src/data/fields/EnumField.ts index d9d36534..306674c4 100644 --- a/app/src/data/fields/EnumField.ts +++ b/app/src/data/fields/EnumField.ts @@ -1,9 +1,9 @@ import { omitKeys } from "core/utils"; -import type { EntityManager } from "data"; +import type { EntityManager } from "data/entities"; import { TransformPersistFailedException } from "../errors"; import { baseFieldConfigSchema, Field, type TActionContext, type TRenderContext } from "./Field"; import type { TFieldTSType } from "data/entities/EntityTypescript"; -import { s } from "core/object/schema"; +import { s } from "bknd/utils"; export const enumFieldConfigSchema = s .strictObject({ diff --git a/app/src/data/fields/Field.ts b/app/src/data/fields/Field.ts index 77756a95..98a1f45d 100644 --- a/app/src/data/fields/Field.ts +++ b/app/src/data/fields/Field.ts @@ -1,10 +1,9 @@ -import { snakeToPascalWithSpaces } from "core/utils"; import type { HTMLInputTypeAttribute, InputHTMLAttributes } from "react"; import type { EntityManager } from "../entities"; import { InvalidFieldConfigException, TransformPersistFailedException } from "../errors"; import type { FieldSpec } from "data/connection/Connection"; import type { TFieldTSType } from "data/entities/EntityTypescript"; -import { s, parse, InvalidSchemaError } from "core/object/schema"; +import { s, parse, InvalidSchemaError, snakeToPascalWithSpaces } from "bknd/utils"; // @todo: contexts need to be reworked // e.g. "table" is irrelevant, because if read is not given, it fails diff --git a/app/src/data/fields/JsonField.ts b/app/src/data/fields/JsonField.ts index c75124a7..711767f9 100644 --- a/app/src/data/fields/JsonField.ts +++ b/app/src/data/fields/JsonField.ts @@ -1,9 +1,9 @@ import { omitKeys } from "core/utils"; -import type { EntityManager } from "data"; +import type { EntityManager } from "data/entities"; import { TransformPersistFailedException } from "../errors"; import { Field, type TActionContext, type TRenderContext, baseFieldConfigSchema } from "./Field"; import type { TFieldTSType } from "data/entities/EntityTypescript"; -import { s } from "core/object/schema"; +import { s } from "bknd/utils"; export const jsonFieldConfigSchema = s .strictObject({ diff --git a/app/src/data/fields/JsonSchemaField.ts b/app/src/data/fields/JsonSchemaField.ts index 002eb4ce..76ad00ad 100644 --- a/app/src/data/fields/JsonSchemaField.ts +++ b/app/src/data/fields/JsonSchemaField.ts @@ -1,10 +1,10 @@ import { type Schema as JsonSchema, Validator } from "@cfworker/json-schema"; import { objectToJsLiteral } from "core/utils"; -import type { EntityManager } from "data"; +import type { EntityManager } from "data/entities"; import { TransformPersistFailedException } from "../errors"; import { Field, type TActionContext, type TRenderContext, baseFieldConfigSchema } from "./Field"; import type { TFieldTSType } from "data/entities/EntityTypescript"; -import { s } from "core/object/schema"; +import { s } from "bknd/utils"; export const jsonSchemaFieldConfigSchema = s .strictObject({ diff --git a/app/src/data/fields/NumberField.ts b/app/src/data/fields/NumberField.ts index 48c9c80e..4f6e53c0 100644 --- a/app/src/data/fields/NumberField.ts +++ b/app/src/data/fields/NumberField.ts @@ -1,8 +1,8 @@ -import type { EntityManager } from "data"; +import type { EntityManager } from "data/entities"; import { TransformPersistFailedException } from "../errors"; import { Field, type TActionContext, type TRenderContext, baseFieldConfigSchema } from "./Field"; import type { TFieldTSType } from "data/entities/EntityTypescript"; -import { s } from "core/object/schema"; +import { s } from "bknd/utils"; import { omitKeys } from "core/utils"; export const numberFieldConfigSchema = s diff --git a/app/src/data/fields/PrimaryField.ts b/app/src/data/fields/PrimaryField.ts index 5bdbeef0..3e1743e5 100644 --- a/app/src/data/fields/PrimaryField.ts +++ b/app/src/data/fields/PrimaryField.ts @@ -1,8 +1,7 @@ -import { config } from "core"; -import { omitKeys, uuidv7 } from "core/utils"; +import { config } from "core/config"; +import { omitKeys, uuidv7, s } from "bknd/utils"; import { Field, baseFieldConfigSchema } from "./Field"; import type { TFieldTSType } from "data/entities/EntityTypescript"; -import { s } from "core/object/schema"; export const primaryFieldTypes = ["integer", "uuid"] as const; export type TPrimaryFieldFormat = (typeof primaryFieldTypes)[number]; diff --git a/app/src/data/fields/TextField.ts b/app/src/data/fields/TextField.ts index df947c63..94674fb7 100644 --- a/app/src/data/fields/TextField.ts +++ b/app/src/data/fields/TextField.ts @@ -1,8 +1,8 @@ -import type { EntityManager } from "data"; +import type { EntityManager } from "data/entities"; import { omitKeys } from "core/utils"; import { TransformPersistFailedException } from "../errors"; import { Field, type TActionContext, baseFieldConfigSchema } from "./Field"; -import { s } from "core/object/schema"; +import { s } from "bknd/utils"; export const textFieldConfigSchema = s .strictObject({ diff --git a/app/src/data/fields/VirtualField.ts b/app/src/data/fields/VirtualField.ts index 9d160275..93dac52a 100644 --- a/app/src/data/fields/VirtualField.ts +++ b/app/src/data/fields/VirtualField.ts @@ -1,5 +1,5 @@ import { Field, baseFieldConfigSchema } from "./Field"; -import { s } from "core/object/schema"; +import { s } from "bknd/utils"; export const virtualFieldConfigSchema = s .strictObject({ diff --git a/app/src/data/fields/field-test-suite.ts b/app/src/data/fields/field-test-suite.ts index 5d24e125..369d41ba 100644 --- a/app/src/data/fields/field-test-suite.ts +++ b/app/src/data/fields/field-test-suite.ts @@ -1,4 +1,4 @@ -import type { BaseFieldConfig, Field, TActionContext } from "data"; +import type { BaseFieldConfig, Field, TActionContext } from "data/fields"; import type { ColumnDataType } from "kysely"; import { omit } from "lodash-es"; import type { TestRunner } from "core/test"; diff --git a/app/src/data/helper.ts b/app/src/data/helper.ts index 79dfeb0b..12c531bb 100644 --- a/app/src/data/helper.ts +++ b/app/src/data/helper.ts @@ -1,4 +1,5 @@ -import type { EntityData, EntityManager, Field } from "data"; +import type { EntityData, EntityManager } from "data/entities"; +import type { Field } from "data/fields"; import { transform } from "lodash-es"; export function getDefaultValues(fields: Field[], data: EntityData): EntityData { diff --git a/app/src/data/index.ts b/app/src/data/index.ts deleted file mode 100644 index 5db48962..00000000 --- a/app/src/data/index.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { MutatorEvents, RepositoryEvents } from "./events"; - -export * from "./fields"; -export * from "./entities"; -export * from "./relations"; -export * from "./schema/SchemaManager"; -export * from "./prototype"; -export * from "./connection"; - -export { - type RepoQuery, - type RepoQueryIn, - getRepoQueryTemplate, - repoQuery, -} from "./server/query"; - -export type { WhereQuery } from "./entities/query/WhereBuilder"; - -export { KyselyPluginRunner } from "./plugins/KyselyPluginRunner"; - -export { constructEntity, constructRelation } from "./schema/constructor"; - -export const DatabaseEvents = { - ...MutatorEvents, - ...RepositoryEvents, -}; -export { MutatorEvents, RepositoryEvents }; - -export * as DataPermissions from "./permissions"; - -export { MediaField, type MediaFieldConfig, type MediaItem } from "media/MediaField"; - -export { libsql } from "./connection/sqlite/libsql/LibsqlConnection"; -export { - genericSqlite, - genericSqliteUtils, - type GenericSqliteConnection, -} from "./connection/sqlite/GenericSqliteConnection"; - -export { - EntityTypescript, - type EntityTypescriptOptions, - type TEntityTSType, - type TFieldTSType, -} from "./entities/EntityTypescript"; diff --git a/app/src/data/permissions/index.ts b/app/src/data/permissions/index.ts index 47a57b38..3db75edc 100644 --- a/app/src/data/permissions/index.ts +++ b/app/src/data/permissions/index.ts @@ -1,4 +1,4 @@ -import { Permission } from "core"; +import { Permission } from "core/security/Permission"; export const entityRead = new Permission("data.entity.read"); export const entityCreate = new Permission("data.entity.create"); diff --git a/app/src/data/relations/EntityRelation.ts b/app/src/data/relations/EntityRelation.ts index f7f01d14..b0c3cc44 100644 --- a/app/src/data/relations/EntityRelation.ts +++ b/app/src/data/relations/EntityRelation.ts @@ -1,4 +1,5 @@ -import { parse } from "core/object/schema"; +import type { PrimaryFieldType } from "bknd"; +import { s, parse } from "bknd/utils"; import type { ExpressionBuilder, SelectQueryBuilder } from "kysely"; import type { Entity, EntityData, EntityManager } from "../entities"; import { @@ -8,8 +9,6 @@ import { } from "../relations"; import type { RepoQuery } from "../server/query"; import type { RelationType } from "./relation-types"; -import type { PrimaryFieldType } from "core"; -import { s } from "core/object/schema"; const directions = ["source", "target"] as const; export type TDirection = (typeof directions)[number]; diff --git a/app/src/data/relations/ManyToManyRelation.ts b/app/src/data/relations/ManyToManyRelation.ts index 81bc6a35..99b57401 100644 --- a/app/src/data/relations/ManyToManyRelation.ts +++ b/app/src/data/relations/ManyToManyRelation.ts @@ -6,7 +6,7 @@ import { EntityRelation, type KyselyQueryBuilder } from "./EntityRelation"; import { EntityRelationAnchor } from "./EntityRelationAnchor"; import { RelationField } from "./RelationField"; import { type RelationType, RelationTypes } from "./relation-types"; -import { s } from "core/object/schema"; +import { s } from "bknd/utils"; export type ManyToManyRelationConfig = s.Static; diff --git a/app/src/data/relations/ManyToOneRelation.ts b/app/src/data/relations/ManyToOneRelation.ts index 77a5a7f4..c5ab4049 100644 --- a/app/src/data/relations/ManyToOneRelation.ts +++ b/app/src/data/relations/ManyToOneRelation.ts @@ -1,5 +1,5 @@ -import type { PrimaryFieldType } from "core"; -import { snakeToPascalWithSpaces } from "core/utils"; +import type { PrimaryFieldType } from "bknd"; +import { snakeToPascalWithSpaces, s } from "bknd/utils"; import type { ExpressionBuilder } from "kysely"; import type { Entity, EntityManager } from "../entities"; import type { RepoQuery } from "../server/query"; @@ -8,7 +8,6 @@ import { EntityRelationAnchor } from "./EntityRelationAnchor"; import { RelationField, type RelationFieldBaseConfig } from "./RelationField"; import type { MutationInstructionResponse } from "./RelationMutator"; import { type RelationType, RelationTypes } from "./relation-types"; -import { s } from "core/object/schema"; /** * Source entity receives the mapping field diff --git a/app/src/data/relations/PolymorphicRelation.ts b/app/src/data/relations/PolymorphicRelation.ts index 3a3d6364..acb4a8c4 100644 --- a/app/src/data/relations/PolymorphicRelation.ts +++ b/app/src/data/relations/PolymorphicRelation.ts @@ -5,7 +5,7 @@ import type { RepoQuery } from "../server/query"; import { EntityRelation, type KyselyJsonFrom, type KyselyQueryBuilder } from "./EntityRelation"; import { EntityRelationAnchor } from "./EntityRelationAnchor"; import { type RelationType, RelationTypes } from "./relation-types"; -import { s } from "core/object/schema"; +import { s } from "bknd/utils"; export type PolymorphicRelationConfig = s.Static; diff --git a/app/src/data/relations/RelationField.ts b/app/src/data/relations/RelationField.ts index 1f7ad8b3..3a2ab075 100644 --- a/app/src/data/relations/RelationField.ts +++ b/app/src/data/relations/RelationField.ts @@ -3,7 +3,7 @@ import { Field, baseFieldConfigSchema } from "../fields"; import type { EntityRelation } from "./EntityRelation"; import type { EntityRelationAnchor } from "./EntityRelationAnchor"; import type { TFieldTSType } from "data/entities/EntityTypescript"; -import { s } from "core/object/schema"; +import { s } from "bknd/utils"; const CASCADES = ["cascade", "set null", "set default", "restrict", "no action"] as const; diff --git a/app/src/data/relations/RelationMutator.ts b/app/src/data/relations/RelationMutator.ts index e8944a5e..48bfb538 100644 --- a/app/src/data/relations/RelationMutator.ts +++ b/app/src/data/relations/RelationMutator.ts @@ -1,4 +1,4 @@ -import type { PrimaryFieldType } from "core"; +import type { PrimaryFieldType } from "bknd"; import type { Entity, EntityManager } from "../entities"; import { type EntityRelation, diff --git a/app/src/data/schema/SchemaManager.ts b/app/src/data/schema/SchemaManager.ts index dafd616f..ab2d15fa 100644 --- a/app/src/data/schema/SchemaManager.ts +++ b/app/src/data/schema/SchemaManager.ts @@ -1,8 +1,8 @@ import type { CompiledQuery, TableMetadata } from "kysely"; -import type { IndexMetadata, SchemaResponse } from "../connection/Connection"; -import type { Entity, EntityManager } from "../entities"; -import { PrimaryField } from "../fields"; -import { $console } from "core/utils"; +import type { IndexMetadata, SchemaResponse } from "data/connection/Connection"; +import type { Entity, EntityManager } from "data/entities"; +import { PrimaryField } from "data/fields"; +import { $console } from "bknd/utils"; type IntrospectedTable = TableMetadata & { indices: IndexMetadata[]; diff --git a/app/src/data/schema/constructor.ts b/app/src/data/schema/constructor.ts index 1da566c5..7742812b 100644 --- a/app/src/data/schema/constructor.ts +++ b/app/src/data/schema/constructor.ts @@ -1,5 +1,6 @@ import { transformObject } from "core/utils"; -import { Entity, type Field } from "data"; +import { Entity } from "data/entities"; +import type { Field } from "data/fields"; import { FIELDS, RELATIONS, type TAppDataEntity, type TAppDataRelation } from "data/data-schema"; export function constructEntity(name: string, entityConfig: TAppDataEntity) { diff --git a/app/src/data/server/query.spec.ts b/app/src/data/server/query.spec.ts index 388b72c8..39925991 100644 --- a/app/src/data/server/query.spec.ts +++ b/app/src/data/server/query.spec.ts @@ -1,6 +1,6 @@ import { test, describe, expect } from "bun:test"; import * as q from "./query"; -import { s as schema, parse as $parse, type ParseOptions } from "core/object/schema"; +import { parse as $parse, type ParseOptions } from "bknd/utils"; const parse = (v: unknown, o: ParseOptions = {}) => $parse(q.repoQuery, v, { diff --git a/app/src/data/server/query.ts b/app/src/data/server/query.ts index 2b629c79..f8ba0c07 100644 --- a/app/src/data/server/query.ts +++ b/app/src/data/server/query.ts @@ -1,4 +1,4 @@ -import { s } from "core/object/schema"; +import { s } from "bknd/utils"; import { WhereBuilder, type WhereQuery } from "data/entities/query/WhereBuilder"; import { isObject, $console } from "core/utils"; import type { anyOf, CoercionOptions, Schema } from "jsonv-ts"; diff --git a/app/src/flows/AppFlows.ts b/app/src/flows/AppFlows.ts index 657ad537..eec33658 100644 --- a/app/src/flows/AppFlows.ts +++ b/app/src/flows/AppFlows.ts @@ -1,9 +1,8 @@ -import { transformObject } from "core/utils"; import { Flow, HttpTrigger } from "flows"; import { Hono } from "hono"; import { Module } from "modules/Module"; import { TASKS, flowsConfigSchema } from "./flows-schema"; -import type { s } from "core/object/schema"; +import { type s, transformObject } from "bknd/utils"; export type AppFlowsSchema = s.Static; export type TAppFlowSchema = AppFlowsSchema["flows"][number]; diff --git a/app/src/flows/flows-schema.ts b/app/src/flows/flows-schema.ts index 46e14450..f430c6c4 100644 --- a/app/src/flows/flows-schema.ts +++ b/app/src/flows/flows-schema.ts @@ -1,6 +1,6 @@ import { transformObject } from "core/utils"; import { TaskMap, TriggerMap } from "flows"; -import { s } from "core/object/schema"; +import { s } from "bknd/utils"; export const TASKS = { ...TaskMap, diff --git a/app/src/flows/flows/triggers/EventTrigger.ts b/app/src/flows/flows/triggers/EventTrigger.ts index c14215b0..f17fd693 100644 --- a/app/src/flows/flows/triggers/EventTrigger.ts +++ b/app/src/flows/flows/triggers/EventTrigger.ts @@ -2,7 +2,7 @@ import type { EventManager } from "core/events"; import type { Flow } from "../Flow"; import { Trigger } from "./Trigger"; import { $console } from "core/utils"; -import { s } from "core/object/schema"; +import { s } from "bknd/utils"; export class EventTrigger extends Trigger { override type = "event"; diff --git a/app/src/flows/flows/triggers/HttpTrigger.ts b/app/src/flows/flows/triggers/HttpTrigger.ts index 19403456..e263682d 100644 --- a/app/src/flows/flows/triggers/HttpTrigger.ts +++ b/app/src/flows/flows/triggers/HttpTrigger.ts @@ -1,7 +1,7 @@ import type { Context, Hono } from "hono"; import type { Flow } from "../Flow"; import { Trigger } from "./Trigger"; -import { s } from "core/object/schema"; +import { s } from "bknd/utils"; const httpMethods = ["GET", "POST", "PUT", "PATCH", "DELETE"] as const; diff --git a/app/src/flows/flows/triggers/Trigger.ts b/app/src/flows/flows/triggers/Trigger.ts index 3414bbc8..bd830321 100644 --- a/app/src/flows/flows/triggers/Trigger.ts +++ b/app/src/flows/flows/triggers/Trigger.ts @@ -1,6 +1,6 @@ import type { Execution } from "../Execution"; import type { Flow } from "../Flow"; -import { s, parse } from "core/object/schema"; +import { s, parse } from "bknd/utils"; export class Trigger { // @todo: remove this diff --git a/app/src/flows/tasks/Task.tsx b/app/src/flows/tasks/Task.tsx index fa9f44bc..e72458b0 100644 --- a/app/src/flows/tasks/Task.tsx +++ b/app/src/flows/tasks/Task.tsx @@ -1,7 +1,7 @@ //import { BkndError, SimpleRenderer } from "core"; import { BkndError } from "core/errors"; -import { s, parse } from "core/object/schema"; +import { s, parse } from "bknd/utils"; import type { InputsMap } from "../flows/Execution"; import { SimpleRenderer } from "core/template/SimpleRenderer"; diff --git a/app/src/flows/tasks/presets/FetchTask.ts b/app/src/flows/tasks/presets/FetchTask.ts index 0108b0ce..251d7d55 100644 --- a/app/src/flows/tasks/presets/FetchTask.ts +++ b/app/src/flows/tasks/presets/FetchTask.ts @@ -1,5 +1,5 @@ import { Task, dynamic } from "../Task"; -import { s } from "core/object/schema"; +import { s } from "bknd/utils"; const FetchMethods = ["GET", "POST", "PUT", "PATCH", "DELETE"]; diff --git a/app/src/flows/tasks/presets/LogTask.ts b/app/src/flows/tasks/presets/LogTask.ts index b3b96b67..63b9677a 100644 --- a/app/src/flows/tasks/presets/LogTask.ts +++ b/app/src/flows/tasks/presets/LogTask.ts @@ -1,6 +1,6 @@ import { Task } from "../Task"; import { $console } from "core/utils"; -import { s } from "core/object/schema"; +import { s } from "bknd/utils"; export class LogTask extends Task { type = "log"; diff --git a/app/src/flows/tasks/presets/RenderTask.ts b/app/src/flows/tasks/presets/RenderTask.ts index e57b01f3..fe2ed648 100644 --- a/app/src/flows/tasks/presets/RenderTask.ts +++ b/app/src/flows/tasks/presets/RenderTask.ts @@ -1,5 +1,5 @@ import { Task } from "../Task"; -import { s } from "core/object/schema"; +import { s } from "bknd/utils"; export class RenderTask> extends Task< typeof RenderTask.schema, diff --git a/app/src/flows/tasks/presets/SubFlowTask.ts b/app/src/flows/tasks/presets/SubFlowTask.ts index e3a19f3f..1526d691 100644 --- a/app/src/flows/tasks/presets/SubFlowTask.ts +++ b/app/src/flows/tasks/presets/SubFlowTask.ts @@ -1,6 +1,6 @@ import { Flow } from "../../flows/Flow"; import { Task, dynamic } from "../Task"; -import { s } from "core/object/schema"; +import { s } from "bknd/utils"; export class SubFlowTask> extends Task< typeof SubFlowTask.schema, diff --git a/app/src/index.ts b/app/src/index.ts index ed12dbb9..27061743 100644 --- a/app/src/index.ts +++ b/app/src/index.ts @@ -36,5 +36,127 @@ export type { BkndConfig } from "bknd/adapter"; export * as middlewares from "modules/middlewares"; export { registries } from "modules/registries"; -export type { MediaFieldSchema } from "media/AppMedia"; -export type { UserFieldSchema } from "auth/AppAuth"; +/** + * Core + */ +export { Exception, BkndError } from "core/errors"; +export { isDebug, env } from "core/env"; +export { type PrimaryFieldType, config, type DB, type AppEntity } from "core/config"; +export { Permission } from "core/security/Permission"; +export { getFlashMessage } from "core/server/flash"; +export * from "core/drivers"; +export { Event, InvalidEventReturn } from "core/events/Event"; +export type { + ListenerMode, + ListenerHandler, +} from "core/events/EventListener"; +export { EventManager, type EmitsEvents, type EventClass } from "core/events/EventManager"; + +/** + * Auth + */ +export { + UserExistsException, + UserNotFoundException, + InvalidCredentialsException, +} from "auth/errors"; +export type { + ProfileExchange, + User, + SafeUser, + CreateUser, + AuthResponse, + UserPool, + AuthAction, + AuthUserResolver, +} from "auth/authenticate/Authenticator"; +export { AuthStrategy } from "auth/authenticate/strategies/Strategy"; +export * as AuthPermissions from "auth/auth-permissions"; + +/** + * Media + */ +export { getExtensionFromName, getRandomizedFilename } from "media/utils"; +export * as StorageEvents from "media/storage/events"; +export * as MediaPermissions from "media/media-permissions"; +export type { FileUploadedEventData } from "media/storage/events"; +export { guess as guessMimeType } from "media/storage/mime-types-tiny"; +export { + Storage, + type FileMeta, + type FileListObject, + type StorageConfig, + type FileBody, + type FileUploadPayload, +} from "media/storage/Storage"; +export { StorageAdapter } from "media/storage/StorageAdapter"; + +/** + * Data + */ +import { MutatorEvents, RepositoryEvents } from "data/events"; +export const DataEvents = { ...MutatorEvents, ...RepositoryEvents }; +export type { + RepoQuery, + RepoQueryIn, +} from "data/server/query"; +export type { WhereQuery } from "data/entities/query/WhereBuilder"; +export { KyselyPluginRunner } from "data/plugins/KyselyPluginRunner"; +export * as DataPermissions from "data/permissions"; +export { libsql } from "data/connection/sqlite/libsql/LibsqlConnection"; +export { + genericSqlite, + genericSqliteUtils, + type GenericSqliteConnection, +} from "data/connection/sqlite/GenericSqliteConnection"; +export { + EntityTypescript, + type EntityTypescriptOptions, + type TEntityTSType, + type TFieldTSType, +} from "data/entities/EntityTypescript"; +export * from "data/fields/Field"; +export * from "data/errors"; +export type { EntityRelation } from "data/relations"; +export type * from "data/entities/Entity"; +export type { EntityManager } from "data/entities/EntityManager"; +export type { SchemaManager } from "data/schema/SchemaManager"; +export { + BaseIntrospector, + Connection, + customIntrospector, + type FieldSpec, + type IndexSpec, + type DbFunctions, + type SchemaResponse, + type ConnQuery, + type ConnQueryResults, +} from "data/connection"; +export { SqliteConnection } from "data/connection/sqlite/SqliteConnection"; +export { SqliteIntrospector } from "data/connection/sqlite/SqliteIntrospector"; +export { SqliteLocalConnection } from "data/connection/sqlite/SqliteLocalConnection"; +export { + text, + number, + date, + datetime, + week, + boolean, + enumm, + json, + jsonSchema, + media, + medium, + make, + entity, + relation, + index, + em, + type InferEntityFields, + type InferFields, + type Simplify, + type InferField, + type InsertSchema, + type Schema, + type FieldSchema, +} from "data/prototype"; diff --git a/app/src/media/AppMedia.ts b/app/src/media/AppMedia.ts index 686b4d2e..9f3c88ce 100644 --- a/app/src/media/AppMedia.ts +++ b/app/src/media/AppMedia.ts @@ -1,15 +1,16 @@ -import type { AppEntity } from "core"; +import type { AppEntity, FileUploadedEventData, StorageAdapter } from "bknd"; import { $console } from "core/utils"; -import type { Entity, EntityManager } from "data"; -import { type FileUploadedEventData, Storage, type StorageAdapter, MediaPermissions } from "media"; +import type { Entity, EntityManager } from "data/entities"; +import { Storage } from "media/storage/Storage"; import { Module } from "modules/Module"; import { type FieldSchema, em, entity } from "../data/prototype"; import { MediaController } from "./api/MediaController"; import { buildMediaSchema, MediaAdapterRegistry, type TAppMediaConfig } from "./media-schema"; import { mediaFields } from "./media-entities"; +import * as MediaPermissions from "media/media-permissions"; export type MediaFieldSchema = FieldSchema; -declare module "core" { +declare module "bknd" { interface Media extends AppEntity, MediaFieldSchema {} interface DB { media: Media; diff --git a/app/src/media/MediaField.ts b/app/src/media/MediaField.ts index 5c2dcc82..5f005bc7 100644 --- a/app/src/media/MediaField.ts +++ b/app/src/media/MediaField.ts @@ -1,5 +1,5 @@ import { Field, baseFieldConfigSchema } from "data/fields"; -import { s } from "core/object/schema"; +import { s } from "bknd/utils"; export const mediaFieldConfigSchema = s .strictObject({ diff --git a/app/src/media/api/MediaApi.ts b/app/src/media/api/MediaApi.ts index 956f2aa4..70cf746a 100644 --- a/app/src/media/api/MediaApi.ts +++ b/app/src/media/api/MediaApi.ts @@ -1,15 +1,15 @@ -import type { FileListObject } from "media"; +import type { FileListObject } from "media/storage/Storage"; import { type BaseModuleApiOptions, ModuleApi, type PrimaryFieldType, type TInput, } from "modules/ModuleApi"; -import type { FileWithPath } from "ui/elements/media/file-selector"; import type { ApiFetcher } from "Api"; export type MediaApiOptions = BaseModuleApiOptions & { upload_fetcher: ApiFetcher; + init?: RequestInit; }; export class MediaApi extends ModuleApi { @@ -17,6 +17,7 @@ export class MediaApi extends ModuleApi { return { basepath: "/api/media", upload_fetcher: fetch, + init: {}, }; } @@ -67,7 +68,7 @@ export class MediaApi extends ModuleApi { } protected uploadFile( - body: File | ReadableStream, + body: File | Blob | ReadableStream, opts?: { filename?: string; path?: TInput; @@ -93,6 +94,7 @@ export class MediaApi extends ModuleApi { } const init = { + ...this.options.init, ...(opts?._init || {}), headers, }; @@ -108,7 +110,7 @@ export class MediaApi extends ModuleApi { } async upload( - item: Request | Response | string | File | ReadableStream, + item: Request | Response | string | File | Blob | ReadableStream, opts: { filename?: string; _init?: Omit; diff --git a/app/src/media/api/MediaController.ts b/app/src/media/api/MediaController.ts index 2caf74a4..5be44fd9 100644 --- a/app/src/media/api/MediaController.ts +++ b/app/src/media/api/MediaController.ts @@ -1,12 +1,13 @@ -import { isDebug } from "core"; -import { HttpStatus, getFileFromContext } from "core/utils"; -import type { StorageAdapter } from "media"; -import { StorageEvents, getRandomizedFilename, MediaPermissions } from "media"; -import { DataPermissions } from "data"; +import { isDebug } from "core/env"; +import type { StorageAdapter } from "media/storage/StorageAdapter"; +import * as DataPermissions from "data/permissions"; import { Controller } from "modules/Controller"; import type { AppMedia } from "../AppMedia"; import { MediaField } from "../MediaField"; -import { jsc, s, describeRoute } from "core/object/schema"; +import * as MediaPermissions from "media/media-permissions"; +import * as StorageEvents from "media/storage/events"; +import { jsc, s, describeRoute, HttpStatus, getFileFromContext } from "bknd/utils"; +import { getRandomizedFilename } from "media/utils"; export class MediaController extends Controller { constructor(private readonly media: AppMedia) { diff --git a/app/src/media/index.ts b/app/src/media/index.ts deleted file mode 100644 index c4610c56..00000000 --- a/app/src/media/index.ts +++ /dev/null @@ -1,25 +0,0 @@ -export { guess as guessMimeType } from "./storage/mime-types-tiny"; -export { - Storage, - type FileMeta, - type FileListObject, - type StorageConfig, - type FileBody, - type FileUploadPayload, -} from "./storage/Storage"; -import { StorageAdapter } from "./storage/StorageAdapter"; -import { - type CloudinaryConfig, - StorageCloudinaryAdapter, -} from "./storage/adapters/cloudinary/StorageCloudinaryAdapter"; -import { type S3AdapterConfig, StorageS3Adapter } from "./storage/adapters/s3/StorageS3Adapter"; - -export { StorageAdapter }; -export { StorageS3Adapter, type S3AdapterConfig, StorageCloudinaryAdapter, type CloudinaryConfig }; - -export * as StorageEvents from "./storage/events"; -export * as MediaPermissions from "./media-permissions"; -export type { FileUploadedEventData } from "./storage/events"; -export * from "./utils"; - -export { adapterTestSuite } from "./storage/adapters/adapter-test-suite"; diff --git a/app/src/media/media-permissions.ts b/app/src/media/media-permissions.ts index 714cc2df..527ce28c 100644 --- a/app/src/media/media-permissions.ts +++ b/app/src/media/media-permissions.ts @@ -1,4 +1,4 @@ -import { Permission } from "core"; +import { Permission } from "core/security/Permission"; export const readFile = new Permission("media.file.read"); export const listFiles = new Permission("media.file.list"); diff --git a/app/src/media/media-schema.ts b/app/src/media/media-schema.ts index d4bfb9e2..984437f1 100644 --- a/app/src/media/media-schema.ts +++ b/app/src/media/media-schema.ts @@ -1,7 +1,8 @@ -import { objectTransform } from "core/utils"; -import { type StorageAdapter, StorageS3Adapter, StorageCloudinaryAdapter } from "media"; -import { s } from "core/object/schema"; -import { Registry, type ClassThatImplements } from "core"; +import { s, objectTransform } from "bknd/utils"; +import { Registry, type ClassThatImplements } from "core/registry/Registry"; +import type { StorageAdapter } from "./storage/StorageAdapter"; +import { StorageS3Adapter } from "./storage/adapters/s3/StorageS3Adapter"; +import { StorageCloudinaryAdapter } from "./storage/adapters/cloudinary/StorageCloudinaryAdapter"; export const MediaAdapterRegistry = new Registry<{ cls: ClassThatImplements; diff --git a/app/src/media/storage/StorageAdapter.ts b/app/src/media/storage/StorageAdapter.ts index d6ceaab0..ee00c670 100644 --- a/app/src/media/storage/StorageAdapter.ts +++ b/app/src/media/storage/StorageAdapter.ts @@ -1,6 +1,5 @@ -import type { FileListObject, FileMeta } from "media"; -import type { FileBody, FileUploadPayload } from "media/storage/Storage"; -import type { s } from "core/object/schema"; +import type { FileListObject, FileMeta, FileBody, FileUploadPayload } from "media/storage/Storage"; +import type { s } from "bknd/utils"; const SYMBOL = Symbol.for("bknd:storage"); diff --git a/app/src/media/storage/adapters/adapter-test-suite.ts b/app/src/media/storage/adapters/adapter-test-suite.ts index 88aa7f7f..1a92d346 100644 --- a/app/src/media/storage/adapters/adapter-test-suite.ts +++ b/app/src/media/storage/adapters/adapter-test-suite.ts @@ -1,6 +1,6 @@ import { retry, type TestRunner } from "core/test"; -import type { StorageAdapter } from "media"; -import { randomString } from "core/utils"; +import type { StorageAdapter } from "media/storage/StorageAdapter"; +import { randomString } from "bknd/utils"; import type { BunFile } from "bun"; export async function adapterTestSuite( diff --git a/app/src/media/storage/adapters/cloudinary/StorageCloudinaryAdapter.ts b/app/src/media/storage/adapters/cloudinary/StorageCloudinaryAdapter.ts index 401f7723..96ec7916 100644 --- a/app/src/media/storage/adapters/cloudinary/StorageCloudinaryAdapter.ts +++ b/app/src/media/storage/adapters/cloudinary/StorageCloudinaryAdapter.ts @@ -1,7 +1,7 @@ import { hash, pickHeaders } from "core/utils"; import type { FileBody, FileListObject, FileMeta } from "../../Storage"; import { StorageAdapter } from "../../StorageAdapter"; -import { s, parse } from "core/object/schema"; +import { s, parse } from "bknd/utils"; export const cloudinaryAdapterConfig = s.object( { diff --git a/app/src/media/storage/adapters/s3/StorageS3Adapter.spec.ts b/app/src/media/storage/adapters/s3/StorageS3Adapter.spec.ts index f0b1b521..5a2fe89d 100644 --- a/app/src/media/storage/adapters/s3/StorageS3Adapter.spec.ts +++ b/app/src/media/storage/adapters/s3/StorageS3Adapter.spec.ts @@ -2,7 +2,7 @@ import { afterAll, beforeAll, describe, expect, test } from "bun:test"; import { StorageS3Adapter } from "./StorageS3Adapter"; import { config } from "dotenv"; -import { adapterTestSuite } from "media"; +import { adapterTestSuite } from "media/storage/adapters/adapter-test-suite"; import { assetsPath } from "../../../../../__test__/helper"; import { bunTestRunner } from "adapter/bun/test"; //import { enableFetchLogging } from "../../helper"; diff --git a/app/src/media/storage/adapters/s3/StorageS3Adapter.ts b/app/src/media/storage/adapters/s3/StorageS3Adapter.ts index a88e31f8..7f3da6e6 100644 --- a/app/src/media/storage/adapters/s3/StorageS3Adapter.ts +++ b/app/src/media/storage/adapters/s3/StorageS3Adapter.ts @@ -6,12 +6,12 @@ import type { ListObjectsV2Request, PutObjectRequest, } from "@aws-sdk/client-s3"; -import { AwsClient, isDebug } from "core"; -import { isFile, pickHeaders2 } from "core/utils"; +import { AwsClient } from "core/clients/aws/AwsClient"; +import { isDebug } from "core/env"; +import { isFile, pickHeaders2, parse, s } from "bknd/utils"; import { transform } from "lodash-es"; import type { FileBody, FileListObject } from "../../Storage"; import { StorageAdapter } from "../../StorageAdapter"; -import { parse, s } from "core/object/schema"; export const s3AdapterConfig = s.object( { diff --git a/app/src/media/storage/mime-types-tiny.ts b/app/src/media/storage/mime-types-tiny.ts index 0718da2a..e7c42bbb 100644 --- a/app/src/media/storage/mime-types-tiny.ts +++ b/app/src/media/storage/mime-types-tiny.ts @@ -26,9 +26,11 @@ export const M = new Map([ ["eps", c.a("postscript")], ["epub", c.a("epub+zip")], ["ini", c.t()], + ["ico", c.i("vnd.microsoft.icon")], ["jar", c.a("java-archive")], ["jsonld", c.a("ld+json")], ["jpg", c.i("jpeg")], + ["js", c.t("javascript")], ["log", c.t()], ["m3u", c.t()], ["m3u8", c.a("vnd.apple.mpegurl")], diff --git a/app/src/media/utils/index.ts b/app/src/media/utils/index.ts index d9867dbd..61f48f21 100644 --- a/app/src/media/utils/index.ts +++ b/app/src/media/utils/index.ts @@ -1,4 +1,4 @@ -import { isFile, randomString } from "core/utils"; +import { isFile, randomString } from "bknd/utils"; import { extension } from "media/storage/mime-types-tiny"; export function getExtensionFromName(filename: string): string | undefined { diff --git a/app/src/modules/Controller.ts b/app/src/modules/Controller.ts index b3f29a07..51ae0260 100644 --- a/app/src/modules/Controller.ts +++ b/app/src/modules/Controller.ts @@ -1,9 +1,8 @@ -import type { App } from "App"; +import type { App, SafeUser } from "bknd"; import { type Context, type Env, Hono } from "hono"; import * as middlewares from "modules/middlewares"; -import type { SafeUser } from "auth"; -import type { EntityManager } from "data"; -import { s } from "core/object/schema"; +import type { EntityManager } from "data/entities"; +import { s } from "bknd/utils"; export interface ServerEnv extends Env { Variables: { @@ -64,7 +63,7 @@ export class Controller { return c.notFound(); } - protected getEntitiesEnum(em: EntityManager) { + protected getEntitiesEnum(em: EntityManager): s.StringSchema { const entities = em.entities.map((e) => e.name); // @todo: current workaround to allow strings (sometimes building is not fast enough to get the entities) return entities.length > 0 ? s.anyOf([s.string({ enum: entities }), s.string()]) : s.string(); diff --git a/app/src/modules/Module.ts b/app/src/modules/Module.ts index 97a22124..126a15ed 100644 --- a/app/src/modules/Module.ts +++ b/app/src/modules/Module.ts @@ -1,11 +1,12 @@ -import type { Guard } from "auth"; -import { type DebugLogger, SchemaObject } from "core"; import type { EventManager } from "core/events"; -import type { Connection, EntityManager } from "data"; +import type { Connection } from "data/connection"; +import type { EntityManager } from "data/entities"; import type { Hono } from "hono"; import type { ServerEnv } from "modules/Controller"; import type { ModuleHelper } from "./ModuleHelper"; -import type { s } from "core/object/schema"; +import { SchemaObject } from "core/object/SchemaObject"; +import type { DebugLogger } from "core/utils/DebugLogger"; +import type { Guard } from "auth/authorize/Guard"; type PartialRec = { [P in keyof T]?: PartialRec }; diff --git a/app/src/modules/ModuleApi.ts b/app/src/modules/ModuleApi.ts index f8a295c4..f89fb999 100644 --- a/app/src/modules/ModuleApi.ts +++ b/app/src/modules/ModuleApi.ts @@ -1,7 +1,6 @@ -import type { PrimaryFieldType } from "core"; -import { $console } from "core/utils"; +import type { PrimaryFieldType } from "bknd"; +import { $console, isPlainObject, encodeSearch } from "bknd/utils"; import { isDebug } from "core/env"; -import { encodeSearch } from "core/utils/reqres"; import type { ApiFetcher } from "Api"; export type { PrimaryFieldType }; @@ -95,7 +94,11 @@ export abstract class ModuleApi) {} diff --git a/app/src/modules/ModuleManager.ts b/app/src/modules/ModuleManager.ts index fbd342ed..21b8bb44 100644 --- a/app/src/modules/ModuleManager.ts +++ b/app/src/modules/ModuleManager.ts @@ -1,10 +1,11 @@ -import { Guard } from "auth"; -import { BkndError, DebugLogger, env } from "core"; -import { $console } from "core/utils"; +import { mark, stripMark, $console, s, objectEach, transformObject } from "bknd/utils"; +import { Guard } from "auth/authorize/Guard"; +import { env } from "core/env"; +import { BkndError } from "core/errors"; +import { DebugLogger } from "core/utils/DebugLogger"; import { EventManager, Event } from "core/events"; import * as $diff from "core/object/diff"; -import { objectEach, transformObject } from "core/utils"; -import type { Connection, Schema } from "data"; +import type { Connection } from "data/connection"; import { EntityManager } from "data/entities/EntityManager"; import * as proto from "data/prototype"; import { TransformPersistFailedException } from "data/errors"; @@ -20,7 +21,6 @@ import { AppMedia } from "../media/AppMedia"; import type { ServerEnv } from "./Controller"; import { Module, type ModuleBuildContext } from "./Module"; import { ModuleHelper } from "./ModuleHelper"; -import { s, mark, stripMark } from "core/object/schema"; export type { ModuleBuildContext }; @@ -107,11 +107,11 @@ const configJsonSchema = s.anyOf([ export const __bknd = proto.entity(TABLE_NAME, { version: proto.number().required(), type: proto.enumm({ enum: ["config", "diff", "backup"] }).required(), - json: proto.jsonSchema({ schema: configJsonSchema }).required(), + json: proto.jsonSchema({ schema: configJsonSchema.toJSON() }).required(), created_at: proto.datetime(), updated_at: proto.datetime(), }); -type ConfigTable2 = Schema; +type ConfigTable2 = proto.Schema; interface T_INTERNAL_EM { __bknd: ConfigTable2; } @@ -267,7 +267,9 @@ export class ModuleManager { ctx(rebuild?: boolean): ModuleBuildContext { if (rebuild) { this.rebuildServer(); - this.em = new EntityManager([], this.connection, [], [], this.emgr); + this.em = this.em + ? this.em.clear() + : new EntityManager([], this.connection, [], [], this.emgr); this.guard = new Guard(); } @@ -694,13 +696,13 @@ export class ModuleManager { return transformObject(this.modules, (module) => module.toJSON(true)) as any; } - getSchema() { + getSchema(): { version: number } & ModuleSchemas { const schemas = transformObject(this.modules, (module) => module.getSchema()); return { version: this.version(), ...schemas, - }; + } as any; } toJSON(secrets?: boolean): { version: number } & ModuleConfigs { @@ -731,9 +733,14 @@ export function getDefaultSchema() { export function getDefaultConfig(): ModuleConfigs { const config = transformObject(MODULES, (module) => { - return module.prototype.getSchema().template(); - //return Default(module.prototype.getSchema(), {}); + return module.prototype.getSchema().template( + {}, + { + withOptional: true, + withExtendedOptional: true, + }, + ); }); - return config as any; + return structuredClone(config) as any; } diff --git a/app/src/modules/permissions/index.ts b/app/src/modules/permissions/index.ts index a2d891db..cc54754c 100644 --- a/app/src/modules/permissions/index.ts +++ b/app/src/modules/permissions/index.ts @@ -1,4 +1,4 @@ -import { Permission } from "core"; +import { Permission } from "core/security/Permission"; export const accessAdmin = new Permission("system.access.admin"); export const accessApi = new Permission("system.access.api"); diff --git a/app/src/modules/server/AdminController.tsx b/app/src/modules/server/AdminController.tsx index 5cb66d6c..d7140988 100644 --- a/app/src/modules/server/AdminController.tsx +++ b/app/src/modules/server/AdminController.tsx @@ -1,8 +1,9 @@ /** @jsxImportSource hono/jsx */ import type { App } from "App"; -import { config, isDebug } from "core"; -import { $console } from "core/utils"; +import { isDebug } from "core/env"; +import { config } from "core/config"; +import { $console } from "bknd/utils"; import { addFlashMessage } from "core/server/flash"; import { html } from "hono/html"; import { Fragment } from "hono/jsx"; diff --git a/app/src/modules/server/AppServer.ts b/app/src/modules/server/AppServer.ts index 09bdb709..6a2f851d 100644 --- a/app/src/modules/server/AppServer.ts +++ b/app/src/modules/server/AppServer.ts @@ -1,9 +1,9 @@ -import { Exception, isDebug } from "core"; -import { $console } from "core/utils"; +import { Exception } from "core/errors"; +import { isDebug } from "core/env"; +import { $console, s } from "bknd/utils"; import { cors } from "hono/cors"; import { Module } from "modules/Module"; import { AuthException } from "auth/errors"; -import { s } from "core/object/schema"; const serverMethods = ["GET", "POST", "PATCH", "PUT", "DELETE"] as const; @@ -17,6 +17,7 @@ export const serverConfigSchema = s.strictObject({ allow_headers: s.array(s.string(), { default: ["Content-Type", "Content-Length", "Authorization", "Accept"], }), + allow_credentials: s.boolean({ default: true }), }), }); @@ -36,12 +37,14 @@ export class AppServer extends Module { } override async build() { + const origin = this.config.cors.origin ?? ""; this.client.use( "*", cors({ - origin: this.config.cors.origin, + origin: origin.includes(",") ? origin.split(",").map((o) => o.trim()) : origin, allowMethods: this.config.cors.allow_methods, allowHeaders: this.config.cors.allow_headers, + credentials: this.config.cors.allow_credentials, }), ); diff --git a/app/src/modules/server/SystemController.ts b/app/src/modules/server/SystemController.ts index d6801074..704da55e 100644 --- a/app/src/modules/server/SystemController.ts +++ b/app/src/modules/server/SystemController.ts @@ -1,8 +1,19 @@ /// import type { App } from "App"; -import { datetimeStringLocal, datetimeStringUTC, getTimezone, getTimezoneOffset, $console } from "core/utils"; -import { getRuntimeKey } from "core/utils"; +import { + datetimeStringLocal, + datetimeStringUTC, + getTimezone, + getTimezoneOffset, + $console, + getRuntimeKey, + SecretSchema, + jsc, + s, + describeRoute, + InvalidSchemaError, +} from "bknd/utils"; import type { Context, Hono } from "hono"; import { Controller } from "modules/Controller"; import { openAPISpecs } from "jsonv-ts/hono"; @@ -14,9 +25,7 @@ import { type ModuleKey, } from "modules/ModuleManager"; import * as SystemPermissions from "modules/permissions"; -import { jsc, s, describeRoute, InvalidSchemaError } from "core/object/schema"; import { getVersion } from "core/env"; -import { SecretSchema } from "core/object/schema/secret"; export type ConfigUpdate = { success: true; @@ -317,6 +326,7 @@ export class SystemController extends Controller { local: datetimeStringLocal(), utc: datetimeStringUTC(), }, + origin: new URL(c.req.raw.url).origin, plugins: Array.from(this.app.plugins.keys()), walk: { auth: [ diff --git a/app/src/plugins/cloudflare/image-optimization.plugin.ts b/app/src/plugins/cloudflare/image-optimization.plugin.ts index 86fb93b1..ab88161b 100644 --- a/app/src/plugins/cloudflare/image-optimization.plugin.ts +++ b/app/src/plugins/cloudflare/image-optimization.plugin.ts @@ -1,15 +1,38 @@ import type { App, AppPlugin } from "bknd"; +import { s, jsc, mergeObject, pickHeaders2 } from "bknd/utils"; + +/** + * check RequestInitCfPropertiesImage + */ +const schema = s.partialObject({ + dpr: s.number({ minimum: 1, maximum: 3 }), + fit: s.string({ enum: ["scale-down", "contain", "cover", "crop", "pad"] }), + format: s.string({ + enum: ["auto", "avif", "webp", "jpeg", "baseline-jpeg", "json"], + default: "auto", + }), + height: s.number(), + width: s.number(), + metadata: s.string({ enum: ["copyright", "keep", "none"] }), + quality: s.number({ minimum: 1, maximum: 100 }), +}); +type ImageOptimizationSchema = s.Static; export type CloudflareImageOptimizationOptions = { accessUrl?: string; resolvePath?: string; - autoFormat?: boolean; + explain?: boolean; + defaultOptions?: ImageOptimizationSchema; + fixedOptions?: ImageOptimizationSchema; + cacheControl?: string; }; export function cloudflareImageOptimization({ accessUrl = "/_plugin/image/optimize", resolvePath = "/api/media/file", - autoFormat = true, + explain = false, + defaultOptions = {}, + fixedOptions = {}, }: CloudflareImageOptimizationOptions = {}): AppPlugin { const disallowedAccessUrls = ["/api", "/admin", "/_optimize"]; if (disallowedAccessUrls.includes(accessUrl) || accessUrl.length < 2) { @@ -19,7 +42,14 @@ export function cloudflareImageOptimization({ return (app: App) => ({ name: "cf-image-optimization", onBuilt: () => { - app.server.get(`${accessUrl}/:path{.+$}`, async (c) => { + if (explain) { + app.server.get(accessUrl, async (c) => { + return c.json({ + searchParams: schema.toJSON(), + }); + }); + } + app.server.get(`${accessUrl}/:path{.+$}`, jsc("query", schema), async (c) => { const request = c.req.raw; const url = new URL(request.url); @@ -34,26 +64,25 @@ export function cloudflareImageOptimization({ } const imageURL = `${url.origin}${resolvePath}/${path}`; - const metadata = await storage.objectMetadata(path); - - // Cloudflare-specific options are in the cf object. - const params = Object.fromEntries(url.searchParams.entries()); - const options: RequestInitCfPropertiesImage = {}; + //const metadata = await storage.objectMetadata(path); // Copy parameters from query string to request options. // You can implement various different parameters here. - if ("fit" in params) options.fit = params.fit as any; - if ("width" in params) options.width = Number.parseInt(params.width); - if ("height" in params) options.height = Number.parseInt(params.height); - if ("quality" in params) options.quality = Number.parseInt(params.quality); + const options = mergeObject( + structuredClone(defaultOptions), + c.req.valid("query"), + structuredClone(fixedOptions), + ); // Your Worker is responsible for automatic format negotiation. Check the Accept header. - if (autoFormat) { - const accept = request.headers.get("Accept")!; - if (/image\/avif/.test(accept)) { - options.format = "avif"; - } else if (/image\/webp/.test(accept)) { - options.format = "webp"; + if (options.format) { + if (options.format === "auto") { + const accept = request.headers.get("Accept")!; + if (/image\/avif/.test(accept)) { + options.format = "avif"; + } else if (/image\/webp/.test(accept)) { + options.format = "webp"; + } } } @@ -63,16 +92,20 @@ export function cloudflareImageOptimization({ }); // Returning fetch() with resizing options will pass through response with the resized image. - const res = await fetch(imageRequest, { cf: { image: options } }); + const res = await fetch(imageRequest, { cf: { image: options as any } }); + const headers = pickHeaders2(res.headers, [ + "Content-Type", + "Content-Length", + "Age", + "Date", + "Last-Modified", + ]); + headers.set("Cache-Control", "public, max-age=31536000, immutable"); return new Response(res.body, { status: res.status, statusText: res.statusText, - headers: { - "Cache-Control": "public, max-age=600", - "Content-Type": metadata.type, - "Content-Length": metadata.size.toString(), - }, + headers, }); }); }, diff --git a/app/src/plugins/dev/sync-types.plugin.ts b/app/src/plugins/dev/sync-types.plugin.ts index aa6756a2..c632e63e 100644 --- a/app/src/plugins/dev/sync-types.plugin.ts +++ b/app/src/plugins/dev/sync-types.plugin.ts @@ -1,5 +1,4 @@ -import { App, type AppPlugin } from "bknd"; -import { EntityTypescript } from "bknd/data"; +import { App, type AppPlugin, EntityTypescript } from "bknd"; export type SyncTypesOptions = { enabled?: boolean; diff --git a/app/src/ui/client/ClientProvider.tsx b/app/src/ui/client/ClientProvider.tsx index 37fe5348..13352d14 100644 --- a/app/src/ui/client/ClientProvider.tsx +++ b/app/src/ui/client/ClientProvider.tsx @@ -1,5 +1,5 @@ import { Api, type ApiOptions, type AuthState } from "Api"; -import { isDebug } from "core"; +import { isDebug } from "core/env"; import { createContext, type ReactNode, useContext, useMemo, useState } from "react"; import type { AdminBkndWindowContext } from "modules/server/AdminController"; diff --git a/app/src/ui/client/api/use-entity.ts b/app/src/ui/client/api/use-entity.ts index 4219c412..907bc332 100644 --- a/app/src/ui/client/api/use-entity.ts +++ b/app/src/ui/client/api/use-entity.ts @@ -1,7 +1,6 @@ -import type { DB, PrimaryFieldType } from "core"; -import { objectTransform } from "core/utils/objects"; -import { encodeSearch } from "core/utils/reqres"; -import type { EntityData, RepoQueryIn, RepositoryResult } from "data"; +import type { DB, PrimaryFieldType, EntityData, RepoQueryIn } from "bknd"; +import { objectTransform, encodeSearch } from "bknd/utils"; +import type { RepositoryResult } from "data/entities"; import type { Insertable, Selectable, Updateable } from "kysely"; import type { FetchPromise, ModuleApi, ResponseObject } from "modules/ModuleApi"; import useSWR, { type SWRConfiguration, type SWRResponse, mutate } from "swr"; diff --git a/app/src/ui/client/index.ts b/app/src/ui/client/index.ts index 500c9bef..2fb520eb 100644 --- a/app/src/ui/client/index.ts +++ b/app/src/ui/client/index.ts @@ -11,4 +11,4 @@ export * from "./api/use-entity"; export { useAuth } from "./schema/auth/use-auth"; export { Api, type TApiUser, type AuthState, type ApiOptions } from "../../Api"; export { FetchPromise } from "modules/ModuleApi"; -export type { RepoQueryIn } from "data"; +export type { RepoQueryIn } from "bknd"; diff --git a/app/src/ui/client/schema/auth/use-auth.ts b/app/src/ui/client/schema/auth/use-auth.ts index e932d782..e3fb4a6f 100644 --- a/app/src/ui/client/schema/auth/use-auth.ts +++ b/app/src/ui/client/schema/auth/use-auth.ts @@ -1,5 +1,5 @@ import type { AuthState } from "Api"; -import type { AuthResponse } from "auth"; +import type { AuthResponse } from "bknd"; import { useApi, useInvalidate } from "ui/client"; import { useClientContext } from "ui/client/ClientProvider"; diff --git a/app/src/ui/client/schema/data/use-bknd-data.ts b/app/src/ui/client/schema/data/use-bknd-data.ts index 723944ef..98ee85ab 100644 --- a/app/src/ui/client/schema/data/use-bknd-data.ts +++ b/app/src/ui/client/schema/data/use-bknd-data.ts @@ -1,5 +1,4 @@ -import { transformObject } from "core/utils"; -import { constructEntity } from "data"; +import { constructEntity } from "data/schema/constructor"; import { type TAppDataEntity, type TAppDataEntityFields, @@ -13,7 +12,7 @@ import { import { useBknd } from "ui/client/bknd"; import type { TSchemaActions } from "ui/client/schema/actions"; import { bkndModals } from "ui/modals"; -import { s, parse, InvalidSchemaError } from "core/object/schema"; +import { s, parse, InvalidSchemaError, transformObject } from "bknd/utils"; export function useBkndData() { const { config, app, schema, actions: bkndActions } = useBknd(); diff --git a/app/src/ui/client/schema/flows/use-flows.ts b/app/src/ui/client/schema/flows/use-flows.ts index 478112ca..2e3dcd5f 100644 --- a/app/src/ui/client/schema/flows/use-flows.ts +++ b/app/src/ui/client/schema/flows/use-flows.ts @@ -1,4 +1,4 @@ -import { parse } from "core/object/schema"; +import { parse } from "bknd/utils"; import { type TAppFlowSchema, flowSchema } from "flows/flows-schema"; import { useBknd } from "../../BkndProvider"; diff --git a/app/src/ui/client/utils/AppReduced.ts b/app/src/ui/client/utils/AppReduced.ts index 4da7b0d9..2085d99c 100644 --- a/app/src/ui/client/utils/AppReduced.ts +++ b/app/src/ui/client/utils/AppReduced.ts @@ -1,5 +1,7 @@ import type { App } from "App"; -import { type Entity, type EntityRelation, constructEntity, constructRelation } from "data"; +import type { Entity } from "data/entities"; +import type { EntityRelation } from "data/relations"; +import { constructEntity, constructRelation } from "data/schema/constructor"; import { RelationAccessor } from "data/relations/RelationAccessor"; import { Flow, TaskMap } from "flows"; import type { BkndAdminOptions } from "ui/client/BkndProvider"; diff --git a/app/src/ui/components/display/ErrorBoundary.tsx b/app/src/ui/components/display/ErrorBoundary.tsx index f829ce7d..ad9dd7d4 100644 --- a/app/src/ui/components/display/ErrorBoundary.tsx +++ b/app/src/ui/components/display/ErrorBoundary.tsx @@ -34,11 +34,13 @@ class ErrorBoundary extends Component { private renderFallback() { if (this.props.fallback) { - return typeof this.props.fallback === "function" - ? this.props.fallback({ error: this.state.error!, resetError: this.resetError }) - : this.props.fallback; + return typeof this.props.fallback === "function" ? ( + this.props.fallback({ error: this.state.error!, resetError: this.resetError }) + ) : ( + {this.props.fallback} + ); } - return Error; + return Error1; } override render() { diff --git a/app/src/ui/components/form/Formy/components.tsx b/app/src/ui/components/form/Formy/components.tsx index 4eb8cb43..64d0f7ae 100644 --- a/app/src/ui/components/form/Formy/components.tsx +++ b/app/src/ui/components/form/Formy/components.tsx @@ -1,6 +1,6 @@ import clsx from "clsx"; import { getBrowser } from "core/utils"; -import type { Field } from "data"; +import type { Field } from "data/fields"; import { Switch as RadixSwitch } from "radix-ui"; import { type ComponentPropsWithoutRef, diff --git a/app/src/ui/components/form/json-schema-form/utils.ts b/app/src/ui/components/form/json-schema-form/utils.ts index 00f8caf3..81b4a928 100644 --- a/app/src/ui/components/form/json-schema-form/utils.ts +++ b/app/src/ui/components/form/json-schema-form/utils.ts @@ -4,10 +4,13 @@ import type { JSONSchema } from "json-schema-to-ts"; import type { JSONSchemaType } from "json-schema-to-ts/lib/types/definitions/jsonSchema"; export { isEqual, getPath } from "core/utils/objects"; -//export { isEqual } from "lodash-es"; + +export function isNotDefined(value: any) { + return value === null || value === undefined || value === ""; +} export function coerce(value: any, schema: JsonSchema, opts?: { required?: boolean }) { - if (!value && typeof opts?.required === "boolean" && !opts.required) { + if (isNotDefined(value) && typeof opts?.required === "boolean" && !opts.required) { return undefined; } diff --git a/app/src/ui/components/form/json-schema/JsonvTsValidator.ts b/app/src/ui/components/form/json-schema/JsonvTsValidator.ts index a3f8b53f..ae744fd8 100644 --- a/app/src/ui/components/form/json-schema/JsonvTsValidator.ts +++ b/app/src/ui/components/form/json-schema/JsonvTsValidator.ts @@ -23,7 +23,7 @@ export class JsonvTsValidator return { errors: [], validationError: null as any }; } - const jsSchema = s.fromSchema(schema as any); + const jsSchema = s.fromSchema(JSON.parse(JSON.stringify(schema)) as any); const result = jsSchema.validate(formData); if (result.valid) { diff --git a/app/src/ui/components/form/native-form/NativeForm.tsx b/app/src/ui/components/form/native-form/NativeForm.tsx index 15b6d5cd..17cc6497 100644 --- a/app/src/ui/components/form/native-form/NativeForm.tsx +++ b/app/src/ui/components/form/native-form/NativeForm.tsx @@ -6,7 +6,6 @@ import { useRef, useState, } from "react"; -import { useEvent } from "ui/hooks/use-event"; import { type CleanOptions, type InputElement, diff --git a/app/src/ui/elements/auth/AuthForm.tsx b/app/src/ui/elements/auth/AuthForm.tsx index 52728df3..0865317f 100644 --- a/app/src/ui/elements/auth/AuthForm.tsx +++ b/app/src/ui/elements/auth/AuthForm.tsx @@ -1,20 +1,11 @@ import type { AppAuthOAuthStrategy, AppAuthSchema } from "auth/auth-schema"; import clsx from "clsx"; -import { Form } from "json-schema-form-react"; +import { NativeForm } from "ui/components/form/native-form/NativeForm"; import { transform } from "lodash-es"; import type { ComponentPropsWithoutRef } from "react"; import { Button } from "ui/components/buttons/Button"; import { Group, Input, Password, Label } from "ui/components/form/Formy/components"; import { SocialLink } from "./SocialLink"; -import type { Validator } from "json-schema-form-react"; -import { s } from "core/object/schema"; -import type { ErrorDetail } from "jsonv-ts"; - -class JsonvTsValidator implements Validator { - async validate(schema: s.Schema, data: any) { - return schema.validate(data).errors; - } -} export type LoginFormProps = Omit, "onSubmit" | "action"> & { className?: string; @@ -25,16 +16,6 @@ export type LoginFormProps = Omit, "onSubmit" | buttonLabel?: string; }; -const validator = new JsonvTsValidator(); -const schema = s.strictObject({ - email: s.string({ - pattern: "^[\\w-\\.]+@([\\w-]+\\.)+[\\w-]{2,4}$", - }), - password: s.string({ - minLength: 8, // @todo: this should be configurable - }), -}); - export function AuthForm({ formData, className, @@ -79,38 +60,31 @@ export function AuthForm({ )} -
- {({ errors, submitting }) => ( - <> - - - - - - - - + + + + + + + + - - - )} -
+ + ); } diff --git a/app/src/ui/elements/media/Dropzone.tsx b/app/src/ui/elements/media/Dropzone.tsx index bc2cb080..b1e4e974 100644 --- a/app/src/ui/elements/media/Dropzone.tsx +++ b/app/src/ui/elements/media/Dropzone.tsx @@ -1,4 +1,4 @@ -import type { DB } from "core"; +import type { DB } from "bknd"; import { type ComponentPropsWithRef, createContext, diff --git a/app/src/ui/elements/media/DropzoneContainer.tsx b/app/src/ui/elements/media/DropzoneContainer.tsx index 1ecd4f23..2f695a94 100644 --- a/app/src/ui/elements/media/DropzoneContainer.tsx +++ b/app/src/ui/elements/media/DropzoneContainer.tsx @@ -1,6 +1,5 @@ import type { Api } from "bknd/client"; -import type { PrimaryFieldType } from "core"; -import type { RepoQueryIn } from "data"; +import type { PrimaryFieldType, RepoQueryIn } from "bknd"; import type { MediaFieldSchema } from "media/AppMedia"; import type { TAppMediaConfig } from "media/media-schema"; import { useId, useEffect, useRef, useState } from "react"; diff --git a/app/src/ui/hooks/use-event.ts b/app/src/ui/hooks/use-event.ts index e55baca5..d68e0308 100644 --- a/app/src/ui/hooks/use-event.ts +++ b/app/src/ui/hooks/use-event.ts @@ -5,7 +5,7 @@ // .current at the right timing." // So we will have to make do with this "close enough" approach for now. import { useLayoutEffect, useRef } from "react"; -import { isDebug } from "core"; +import { isDebug } from "core/env"; export const useEvent = (fn: Fn): Fn => { if (isDebug()) { diff --git a/app/src/ui/hooks/use-search.ts b/app/src/ui/hooks/use-search.ts index 64d162a8..40967c15 100644 --- a/app/src/ui/hooks/use-search.ts +++ b/app/src/ui/hooks/use-search.ts @@ -1,7 +1,6 @@ -import { decodeSearch, encodeSearch, mergeObject } from "core/utils"; +import { decodeSearch, encodeSearch, mergeObject, type s, parse } from "bknd/utils"; import { isEqual, transform } from "lodash-es"; import { useLocation, useSearch as useWouterSearch } from "wouter"; -import { type s, parse } from "core/object/schema"; import { useEffect, useMemo, useState } from "react"; export type UseSearchOptions = { diff --git a/app/src/ui/lib/routes.ts b/app/src/ui/lib/routes.ts index 42b896ba..7243099a 100644 --- a/app/src/ui/lib/routes.ts +++ b/app/src/ui/lib/routes.ts @@ -1,5 +1,5 @@ -import type { PrimaryFieldType } from "core"; -import { encodeSearch } from "core/utils"; +import type { PrimaryFieldType } from "bknd"; +import { encodeSearch } from "bknd/utils"; import { useLocation, useRouter } from "wouter"; import { useBknd } from "../client/BkndProvider"; diff --git a/app/src/ui/modals/debug/SchemaFormModal.tsx b/app/src/ui/modals/debug/SchemaFormModal.tsx index 5a64e8b5..dc8eece0 100644 --- a/app/src/ui/modals/debug/SchemaFormModal.tsx +++ b/app/src/ui/modals/debug/SchemaFormModal.tsx @@ -65,7 +65,7 @@ export function SchemaFormModal({ ; @@ -162,11 +155,11 @@ function EntityFormField({ fieldApi, field, action, data, ...props }: EntityForm //const required = field.isRequired(); //const customFieldProps = { ...props, action, required }; - if (field instanceof RelationField) { + if (field.type === "relation") { return ( ; + if (field.type === "json") { + return ; } - if (field instanceof JsonSchemaField) { + if (field.type === "jsonschema") { return ( ; + if (field.type === "enum") { + return ; } const fieldElement = field.getHtmlConfig().element; diff --git a/app/src/ui/modules/data/components/EntityTable.tsx b/app/src/ui/modules/data/components/EntityTable.tsx index a45e07b5..87c2bf10 100644 --- a/app/src/ui/modules/data/components/EntityTable.tsx +++ b/app/src/ui/modules/data/components/EntityTable.tsx @@ -1,5 +1,5 @@ import { useToggle } from "@mantine/hooks"; -import type { Entity, EntityData } from "data"; +import type { Entity, EntityData } from "bknd"; import { TbArrowDown, TbArrowUp, diff --git a/app/src/ui/modules/data/components/EntityTable2.tsx b/app/src/ui/modules/data/components/EntityTable2.tsx index c94f2e9d..6b03d6a6 100644 --- a/app/src/ui/modules/data/components/EntityTable2.tsx +++ b/app/src/ui/modules/data/components/EntityTable2.tsx @@ -1,4 +1,4 @@ -import type { Entity, EntityData } from "data"; +import type { Entity, EntityData } from "bknd"; import { CellValue, DataTable, type DataTableProps } from "ui/components/table/DataTable"; import ErrorBoundary from "ui/components/display/ErrorBoundary"; diff --git a/app/src/ui/modules/data/components/fields/EntityJsonSchemaFormField.tsx b/app/src/ui/modules/data/components/fields/EntityJsonSchemaFormField.tsx index 6fb3ee38..6752a978 100644 --- a/app/src/ui/modules/data/components/fields/EntityJsonSchemaFormField.tsx +++ b/app/src/ui/modules/data/components/fields/EntityJsonSchemaFormField.tsx @@ -1,4 +1,5 @@ -import type { EntityData, JsonSchemaField } from "data"; +import type { EntityData } from "bknd"; +import type { JsonSchemaField } from "data/fields"; import * as Formy from "ui/components/form/Formy"; import { FieldLabel } from "ui/components/form/Formy"; import { JsonSchemaForm } from "ui/components/form/json-schema"; diff --git a/app/src/ui/modules/data/components/fields/EntityRelationalFormField.tsx b/app/src/ui/modules/data/components/fields/EntityRelationalFormField.tsx index 5de71bdc..941f8992 100644 --- a/app/src/ui/modules/data/components/fields/EntityRelationalFormField.tsx +++ b/app/src/ui/modules/data/components/fields/EntityRelationalFormField.tsx @@ -1,6 +1,7 @@ import { getHotkeyHandler, useHotkeys } from "@mantine/hooks"; import { ucFirst } from "core/utils"; -import type { EntityData, RelationField } from "data"; +import type { EntityData } from "bknd"; +import type { RelationField } from "data/relations"; import { useEffect, useRef, useState } from "react"; import { TbEye } from "react-icons/tb"; import { useEntityQuery } from "ui/client"; diff --git a/app/src/ui/modules/data/components/schema/create-modal/CreateModal.tsx b/app/src/ui/modules/data/components/schema/create-modal/CreateModal.tsx index d7ca1839..c28cc40b 100644 --- a/app/src/ui/modules/data/components/schema/create-modal/CreateModal.tsx +++ b/app/src/ui/modules/data/components/schema/create-modal/CreateModal.tsx @@ -1,6 +1,5 @@ import type { ModalProps } from "@mantine/core"; import type { ContextModalProps } from "@mantine/modals"; -import { entitiesSchema, fieldsSchema, relationsSchema } from "data/data-schema"; import { useState } from "react"; import { type Modal2Ref, ModalBody, ModalFooter, ModalTitle } from "ui/components/modal/Modal2"; import { Step, Steps, useStepContext } from "ui/components/steps/Steps"; @@ -10,52 +9,10 @@ import { StepEntityFields } from "./step.entity.fields"; import { StepRelation } from "./step.relation"; import { StepSelect } from "./step.select"; import Templates from "./templates/register"; -import { s } from "core/object/schema"; +import type { TCreateModalSchema } from "./schema"; export type CreateModalRef = Modal2Ref; -export const ModalActions = ["entity", "relation", "media"] as const; - -export const entitySchema = s.object({ - name: s.string(), - ...entitiesSchema.properties, -}); - -// @todo: this union is not fully working, just "string" -const schemaAction = s.anyOf([ - s.string({ enum: ["entity", "relation", "media"] }), - s.string({ pattern: "^template-" }), -]); -export type TSchemaAction = s.Static; - -const createFieldSchema = s.object({ - entity: s.string(), - name: s.string(), - field: s.array(fieldsSchema), -}); -export type TFieldCreate = s.Static; - -const createModalSchema = s.strictObject({ - action: schemaAction, - initial: s.any().optional(), - entities: s - .object({ - create: s.array(entitySchema).optional(), - }) - .optional(), - relations: s - .object({ - create: s.array(s.anyOf(relationsSchema)).optional(), - }) - .optional(), - fields: s - .object({ - create: s.array(createFieldSchema).optional(), - }) - .optional(), -}); -export type TCreateModalSchema = s.Static; - export function CreateModal({ context, id, @@ -107,4 +64,4 @@ CreateModal.modalProps = { padding: 0, } satisfies Partial; -export { ModalBody, ModalFooter, ModalTitle, useStepContext, relationsSchema }; +export { ModalBody, ModalFooter, ModalTitle, useStepContext }; diff --git a/app/src/ui/modules/data/components/schema/create-modal/schema.ts b/app/src/ui/modules/data/components/schema/create-modal/schema.ts new file mode 100644 index 00000000..295b4986 --- /dev/null +++ b/app/src/ui/modules/data/components/schema/create-modal/schema.ts @@ -0,0 +1,44 @@ +import { s } from "bknd/utils"; +import { entitiesSchema, fieldsSchema, relationsSchema } from "data/data-schema"; + +export const ModalActions = ["entity", "relation", "media"] as const; + +export const entitySchema = s.object({ + ...entitiesSchema.properties, + name: s.string(), +}); + +// @todo: this union is not fully working, just "string" +const schemaAction = s.anyOf([ + s.string({ enum: ["entity", "relation", "media"] }), + s.string({ pattern: "^template-" }), +]); +export type TSchemaAction = s.Static; + +const createFieldSchema = s.object({ + entity: s.string(), + name: s.string(), + field: s.array(fieldsSchema), +}); +export type TFieldCreate = s.Static; + +const createModalSchema = s.strictObject({ + action: schemaAction, + initial: s.any().optional(), + entities: s + .object({ + create: s.array(entitySchema).optional(), + }) + .optional(), + relations: s + .object({ + create: s.array(s.anyOf(relationsSchema)).optional(), + }) + .optional(), + fields: s + .object({ + create: s.array(createFieldSchema).optional(), + }) + .optional(), +}); +export type TCreateModalSchema = s.Static; diff --git a/app/src/ui/modules/data/components/schema/create-modal/step.create.tsx b/app/src/ui/modules/data/components/schema/create-modal/step.create.tsx index fe7b3899..0ef9c485 100644 --- a/app/src/ui/modules/data/components/schema/create-modal/step.create.tsx +++ b/app/src/ui/modules/data/components/schema/create-modal/step.create.tsx @@ -1,7 +1,6 @@ import { useDisclosure } from "@mantine/hooks"; import { IconAlignJustified, - IconAugmentedReality, IconBox, IconCirclesRelation, IconInfoCircle, @@ -15,7 +14,7 @@ import { IconButton, type IconType } from "ui/components/buttons/IconButton"; import { JsonViewer } from "ui/components/code/JsonViewer"; import { ModalBody, ModalFooter } from "ui/components/modal/Modal2"; import { useStepContext } from "ui/components/steps/Steps"; -import type { TCreateModalSchema } from "ui/modules/data/components/schema/create-modal/CreateModal"; +import type { TCreateModalSchema } from "ui/modules/data/components/schema/create-modal/schema"; type ActionItem = SummaryItemProps & { run: () => Promise; @@ -35,9 +34,9 @@ export function StepCreate() { action: "add", Icon: IconBox, type: "Entity", - name: entity.name, + name: entity.name!, json: entity, - run: async () => await $data.actions.entity.add(entity.name, entity), + run: async () => await $data.actions.entity.add(entity.name!, entity), })), ); } diff --git a/app/src/ui/modules/data/components/schema/create-modal/step.entity.fields.tsx b/app/src/ui/modules/data/components/schema/create-modal/step.entity.fields.tsx index c52ceb5b..b066dd1b 100644 --- a/app/src/ui/modules/data/components/schema/create-modal/step.entity.fields.tsx +++ b/app/src/ui/modules/data/components/schema/create-modal/step.entity.fields.tsx @@ -1,5 +1,4 @@ -//import { typeboxResolver } from "@hookform/resolvers/typebox"; -import { objectCleanEmpty } from "core/utils"; +import { objectCleanEmpty, type s } from "bknd/utils"; import { type TAppDataEntityFields, entitiesSchema } from "data/data-schema"; import { mergeWith } from "lodash-es"; import { useRef } from "react"; @@ -10,12 +9,12 @@ import { EntityFieldsForm, type EntityFieldsFormRef, } from "ui/routes/data/forms/entity.fields.form"; -import { ModalBody, ModalFooter, type TCreateModalSchema, useStepContext } from "./CreateModal"; +import { ModalBody, ModalFooter, useStepContext } from "./CreateModal"; import { useBkndData } from "ui/client/schema/data/use-bknd-data"; -import type { s } from "core/object/schema"; import { standardSchemaResolver } from "@hookform/resolvers/standard-schema"; +import { entitySchema, type TCreateModalSchema } from "./schema"; -const schema = entitiesSchema; +const schema = entitySchema; type Schema = s.Static; export function StepEntityFields() { diff --git a/app/src/ui/modules/data/components/schema/create-modal/step.entity.tsx b/app/src/ui/modules/data/components/schema/create-modal/step.entity.tsx index 6c105f9a..4384c3ad 100644 --- a/app/src/ui/modules/data/components/schema/create-modal/step.entity.tsx +++ b/app/src/ui/modules/data/components/schema/create-modal/step.entity.tsx @@ -2,30 +2,29 @@ import { standardSchemaResolver } from "@hookform/resolvers/standard-schema"; import { TextInput, Textarea } from "@mantine/core"; import { useFocusTrap } from "@mantine/hooks"; import { useForm } from "react-hook-form"; -import { - ModalBody, - ModalFooter, - type TCreateModalSchema, - entitySchema, - useStepContext, -} from "./CreateModal"; +import { ModalBody, ModalFooter, useStepContext } from "./CreateModal"; +import { entitySchema, type TCreateModalSchema } from "./schema"; +import { s } from "bknd/utils"; +import { cloneSchema } from "core/utils/schema"; + +const schema = s.object({ + name: entitySchema.properties.name, + config: entitySchema.properties.config.partial().optional(), +}); +type Schema = s.Static; export function StepEntity() { const focusTrapRef = useFocusTrap(); const { nextStep, stepBack, state, setState } = useStepContext(); const { register, handleSubmit, formState, watch, control } = useForm({ - mode: "onTouched", - resolver: standardSchemaResolver(entitySchema), - defaultValues: state.entities?.create?.[0] ?? {}, + mode: "onChange", + resolver: standardSchemaResolver(cloneSchema(schema)), + defaultValues: (state.entities?.create?.[0] ?? {}) as Schema, }); - /*const data = watch(); - console.log("state", { isValid }); - console.log("schema", JSON.stringify(entitySchema)); - console.log("data", JSON.stringify(data));*/ function onSubmit(data: any) { - console.log(data); + console.log("onSubmit", data); setState((prev) => { const prevEntity = prev.entities?.create?.[0]; if (prevEntity && prevEntity.name !== data.name) { @@ -45,6 +44,7 @@ export function StepEntity() { <>
+ (); diff --git a/app/src/ui/modules/data/components/schema/create-modal/templates/media/template.media.component.tsx b/app/src/ui/modules/data/components/schema/create-modal/templates/media/template.media.component.tsx index 4137ab43..9f710066 100644 --- a/app/src/ui/modules/data/components/schema/create-modal/templates/media/template.media.component.tsx +++ b/app/src/ui/modules/data/components/schema/create-modal/templates/media/template.media.component.tsx @@ -1,5 +1,5 @@ import { Radio, TextInput } from "@mantine/core"; -import { transformObject } from "core/utils"; +import { transformObject, s, stringIdentifier } from "bknd/utils"; import type { MediaFieldConfig } from "media/MediaField"; import { useEffect, useState } from "react"; import { useForm } from "react-hook-form"; @@ -7,14 +7,8 @@ import { useBknd } from "ui/client/bknd"; import { MantineNumberInput } from "ui/components/form/hook-form-mantine/MantineNumberInput"; import { MantineRadio } from "ui/components/form/hook-form-mantine/MantineRadio"; import { MantineSelect } from "ui/components/form/hook-form-mantine/MantineSelect"; -import { - ModalBody, - ModalFooter, - type TCreateModalSchema, - type TFieldCreate, - useStepContext, -} from "../../CreateModal"; -import { s, stringIdentifier } from "core/object/schema"; +import { ModalBody, ModalFooter, useStepContext } from "../../CreateModal"; +import type { TCreateModalSchema, TFieldCreate } from "../../schema"; import { standardSchemaResolver } from "@hookform/resolvers/standard-schema"; const schema = s.object({ diff --git a/app/src/ui/modules/data/hooks/useEntityForm.tsx b/app/src/ui/modules/data/hooks/useEntityForm.tsx index f13d1bc6..1d9411c4 100644 --- a/app/src/ui/modules/data/hooks/useEntityForm.tsx +++ b/app/src/ui/modules/data/hooks/useEntityForm.tsx @@ -1,5 +1,5 @@ import { useForm } from "@tanstack/react-form"; -import type { Entity, EntityData } from "data"; +import type { Entity, EntityData } from "bknd"; import { getChangeSet, getDefaultValues } from "data/helper"; type EntityFormProps = { diff --git a/app/src/ui/modules/flows/components/TriggerComponent.tsx b/app/src/ui/modules/flows/components/TriggerComponent.tsx index 058b1499..18875882 100644 --- a/app/src/ui/modules/flows/components/TriggerComponent.tsx +++ b/app/src/ui/modules/flows/components/TriggerComponent.tsx @@ -4,7 +4,7 @@ import { type Trigger, TriggerMap } from "flows"; import type { IconType } from "react-icons"; import { TbCircleLetterT } from "react-icons/tb"; import { JsonSchemaForm } from "ui/components/form/json-schema"; -import { s } from "core/object/schema"; +import { s } from "bknd/utils"; export type TaskComponentProps = NodeProps> & { Icon?: IconType; diff --git a/app/src/ui/modules/flows/components/form/TemplateField.tsx b/app/src/ui/modules/flows/components/form/TemplateField.tsx index 6e2506b4..a7de5dc0 100644 --- a/app/src/ui/modules/flows/components/form/TemplateField.tsx +++ b/app/src/ui/modules/flows/components/form/TemplateField.tsx @@ -1,6 +1,6 @@ import type { FieldProps, FormContextType, RJSFSchema, StrictRJSFSchema } from "@rjsf/utils"; -import { SimpleRenderer } from "core"; -import { ucFirst, ucFirstAll } from "core/utils"; +import { SimpleRenderer } from "core/template/SimpleRenderer"; +import { ucFirst, ucFirstAll } from "bknd/utils"; import { useState } from "react"; const modes = ["field", "code"] as const; diff --git a/app/src/ui/modules/flows/components2/nodes/tasks/FetchTaskNode.tsx b/app/src/ui/modules/flows/components2/nodes/tasks/FetchTaskNode.tsx index 52339c23..1cde928a 100644 --- a/app/src/ui/modules/flows/components2/nodes/tasks/FetchTaskNode.tsx +++ b/app/src/ui/modules/flows/components2/nodes/tasks/FetchTaskNode.tsx @@ -2,7 +2,7 @@ import { Input, TextInput } from "@mantine/core"; import { useToggle } from "@mantine/hooks"; import { IconMinus, IconPlus, IconWorld } from "@tabler/icons-react"; import type { Node, NodeProps } from "@xyflow/react"; -import { s } from "core/object/schema"; +import { s } from "bknd/utils"; import { FetchTask } from "flows"; import { useState } from "react"; import { useForm } from "react-hook-form"; diff --git a/app/src/ui/modules/flows/components2/nodes/triggers/TriggerNode.tsx b/app/src/ui/modules/flows/components2/nodes/triggers/TriggerNode.tsx index ad0979d4..4bce7a71 100644 --- a/app/src/ui/modules/flows/components2/nodes/triggers/TriggerNode.tsx +++ b/app/src/ui/modules/flows/components2/nodes/triggers/TriggerNode.tsx @@ -10,7 +10,7 @@ import { MantineSelect } from "ui/components/form/hook-form-mantine/MantineSelec import { useFlowCanvas, useFlowSelector } from "../../../hooks/use-flow"; import { BaseNode } from "../BaseNode"; import { Handle } from "../Handle"; -import { s } from "core/object/schema"; +import { s } from "bknd/utils"; import { standardSchemaResolver } from "@hookform/resolvers/standard-schema"; const schema = s.object({ diff --git a/app/src/ui/routes/auth/auth.roles.tsx b/app/src/ui/routes/auth/auth.roles.tsx index 004e76ca..fa0e521c 100644 --- a/app/src/ui/routes/auth/auth.roles.tsx +++ b/app/src/ui/routes/auth/auth.roles.tsx @@ -1,4 +1,9 @@ -import { transformObject, ucFirstAllSnakeToPascalWithSpaces } from "core/utils"; +import { + transformObject, + ucFirstAllSnakeToPascalWithSpaces, + s, + stringIdentifier, +} from "bknd/utils"; import { useBkndAuth } from "ui/client/schema/auth/use-bknd-auth"; import { Alert } from "ui/components/display/Alert"; import { bkndModals } from "ui/modals"; @@ -6,7 +11,6 @@ import { Button } from "../../components/buttons/Button"; import { CellValue, DataTable } from "../../components/table/DataTable"; import * as AppShell from "../../layouts/AppShell/AppShell"; import { routes, useNavigate } from "../../lib/routes"; -import { stringIdentifier } from "core/object/schema"; export function AuthRolesList() { const [navigate] = useNavigate(); @@ -29,13 +33,9 @@ export function AuthRolesList() { bkndModals.open( "form", { - schema: { - type: "object", - properties: { - name: stringIdentifier, - }, - required: ["name"], - }, + schema: s.strictObject({ + name: stringIdentifier, + }), uiSchema: { name: { "ui:title": "Role name", diff --git a/app/src/ui/routes/auth/auth.settings.tsx b/app/src/ui/routes/auth/auth.settings.tsx index 4a3cced5..1ec1403e 100644 --- a/app/src/ui/routes/auth/auth.settings.tsx +++ b/app/src/ui/routes/auth/auth.settings.tsx @@ -1,5 +1,5 @@ import clsx from "clsx"; -import { isDebug } from "core"; +import { isDebug } from "core/env"; import { TbChevronDown, TbChevronUp } from "react-icons/tb"; import { useBknd } from "ui/client/BkndProvider"; import { useBkndAuth } from "ui/client/schema/auth/use-bknd-auth"; diff --git a/app/src/ui/routes/auth/auth.strategies.tsx b/app/src/ui/routes/auth/auth.strategies.tsx index ca047f6a..205f95eb 100644 --- a/app/src/ui/routes/auth/auth.strategies.tsx +++ b/app/src/ui/routes/auth/auth.strategies.tsx @@ -1,4 +1,4 @@ -import { isDebug } from "core"; +import { isDebug } from "core/env"; import { autoFormatString } from "core/utils"; import { type ChangeEvent, useState } from "react"; import { diff --git a/app/src/ui/routes/auth/forms/role.form.tsx b/app/src/ui/routes/auth/forms/role.form.tsx index f811584c..0a16d2de 100644 --- a/app/src/ui/routes/auth/forms/role.form.tsx +++ b/app/src/ui/routes/auth/forms/role.form.tsx @@ -1,12 +1,11 @@ import { Input, Switch, Tooltip } from "@mantine/core"; import { guardRoleSchema } from "auth/auth-schema"; -import { ucFirst } from "core/utils"; +import { ucFirst, type s } from "bknd/utils"; import { forwardRef, useImperativeHandle } from "react"; import { type UseControllerProps, useController, useForm } from "react-hook-form"; import { useBknd } from "ui/client/bknd"; import { Button } from "ui/components/buttons/Button"; import { MantineSwitch } from "ui/components/form/hook-form-mantine/MantineSwitch"; -import type { s } from "core/object/schema"; import { standardSchemaResolver } from "@hookform/resolvers/standard-schema"; const schema = guardRoleSchema; diff --git a/app/src/ui/routes/data/_data.root.tsx b/app/src/ui/routes/data/_data.root.tsx index a72be123..344ef695 100644 --- a/app/src/ui/routes/data/_data.root.tsx +++ b/app/src/ui/routes/data/_data.root.tsx @@ -9,7 +9,7 @@ import { IconSettings, IconSwitchHorizontal, } from "@tabler/icons-react"; -import type { Entity, TEntityType } from "data"; +import type { Entity, TEntityType } from "bknd"; import { TbDatabasePlus } from "react-icons/tb"; import { twMerge } from "tailwind-merge"; import { useBkndData } from "ui/client/schema/data/use-bknd-data"; diff --git a/app/src/ui/routes/data/data.$entity.$id.tsx b/app/src/ui/routes/data/data.$entity.$id.tsx index e531452d..f4f5cd6f 100644 --- a/app/src/ui/routes/data/data.$entity.$id.tsx +++ b/app/src/ui/routes/data/data.$entity.$id.tsx @@ -1,6 +1,6 @@ -import type { PrimaryFieldType } from "core"; -import { ucFirst } from "core/utils"; -import type { Entity, EntityData, EntityRelation } from "data"; +import type { PrimaryFieldType } from "bknd"; +import { ucFirst } from "bknd/utils"; +import type { Entity, EntityData, EntityRelation } from "bknd"; import { Fragment, useState } from "react"; import { TbDots } from "react-icons/tb"; import { useApiQuery, useEntityQuery } from "ui/client"; diff --git a/app/src/ui/routes/data/data.$entity.create.tsx b/app/src/ui/routes/data/data.$entity.create.tsx index 07b2bb58..62ba3dfc 100644 --- a/app/src/ui/routes/data/data.$entity.create.tsx +++ b/app/src/ui/routes/data/data.$entity.create.tsx @@ -1,4 +1,4 @@ -import type { EntityData } from "data"; +import type { EntityData } from "bknd"; import { useState } from "react"; import { useEntityMutate } from "ui/client"; import { useBkndData } from "ui/client/schema/data/use-bknd-data"; @@ -11,7 +11,7 @@ import { Breadcrumbs2 } from "ui/layouts/AppShell/Breadcrumbs2"; import { routes, useNavigate } from "ui/lib/routes"; import { EntityForm } from "ui/modules/data/components/EntityForm"; import { useEntityForm } from "ui/modules/data/hooks/useEntityForm"; -import { s } from "core/object/schema"; +import { s } from "bknd/utils"; export function DataEntityCreate({ params }) { const { $data } = useBkndData(); diff --git a/app/src/ui/routes/data/data.$entity.index.tsx b/app/src/ui/routes/data/data.$entity.index.tsx index 7b23feed..09a604e7 100644 --- a/app/src/ui/routes/data/data.$entity.index.tsx +++ b/app/src/ui/routes/data/data.$entity.index.tsx @@ -1,4 +1,5 @@ -import { type Entity, repoQuery } from "data"; +import type { Entity } from "bknd"; +import { repoQuery } from "data/server/query"; import { Fragment } from "react"; import { TbDots } from "react-icons/tb"; import { useApiQuery } from "ui/client"; @@ -14,7 +15,7 @@ import * as AppShell from "ui/layouts/AppShell/AppShell"; import { routes, useNavigate } from "ui/lib/routes"; import { useCreateUserModal } from "ui/modules/auth/hooks/use-create-user-modal"; import { EntityTable2 } from "ui/modules/data/components/EntityTable2"; -import { s } from "core/object/schema"; +import { s } from "bknd/utils"; import { pick } from "core/utils/objects"; const searchSchema = s.partialObject({ diff --git a/app/src/ui/routes/data/data.schema.$entity.tsx b/app/src/ui/routes/data/data.schema.$entity.tsx index 3125a349..99f2d135 100644 --- a/app/src/ui/routes/data/data.schema.$entity.tsx +++ b/app/src/ui/routes/data/data.schema.$entity.tsx @@ -4,8 +4,8 @@ import { IconCirclesRelation, IconSettings, } from "@tabler/icons-react"; -import { isDebug } from "core"; -import type { Entity } from "data"; +import { isDebug } from "core/env"; +import type { Entity } from "bknd"; import { cloneDeep } from "lodash-es"; import { useRef, useState } from "react"; import { 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 917fe6fc..d8f7d53b 100644 --- a/app/src/ui/routes/data/forms/entity.fields.form.tsx +++ b/app/src/ui/routes/data/forms/entity.fields.form.tsx @@ -1,5 +1,11 @@ import { Tabs, TextInput, Textarea, Tooltip, Switch } from "@mantine/core"; -import { objectCleanEmpty, omitKeys, ucFirstAllSnakeToPascalWithSpaces } from "core/utils"; +import { + objectCleanEmpty, + omitKeys, + ucFirstAllSnakeToPascalWithSpaces, + s, + stringIdentifier, +} from "bknd/utils"; import { type TAppDataEntityFields, fieldsSchemaObject as originalFieldsSchemaObject, @@ -21,8 +27,8 @@ import { dataFieldsUiSchema } from "../../settings/routes/data.settings"; import { useRoutePathState } from "ui/hooks/use-route-path-state"; import { MantineSelect } from "ui/components/form/hook-form-mantine/MantineSelect"; import type { TPrimaryFieldFormat } from "data/fields/PrimaryField"; -import { s, stringIdentifier } from "core/object/schema"; import { standardSchemaResolver } from "@hookform/resolvers/standard-schema"; +import ErrorBoundary from "ui/components/display/ErrorBoundary"; const fieldsSchemaObject = originalFieldsSchemaObject; const fieldsSchema = s.anyOf(Object.values(fieldsSchemaObject)); @@ -453,19 +459,17 @@ function EntityField({
- { - setValue(`${prefix}.config`, { - ...getValues([`fields.${index}.config`])[0], - ...value, - }); - }} - /> + + { + setValue(`${prefix}.config`, { + ...getValues([`fields.${index}.config`])[0], + ...value, + }); + }} + /> +
@@ -490,3 +494,25 @@ function EntityField({ ); } + +const SpecificForm = ({ + field, + onChange, +}: { + field: FieldArrayWithId; + onChange: (value: any) => void; +}) => { + const type = field.field.type; + const specificData = omit(field.field.config, commonProps); + + return ( + + ); +}; diff --git a/app/src/ui/routes/flows/_flows.root.tsx b/app/src/ui/routes/flows/_flows.root.tsx index bb1e7a80..681a6e26 100644 --- a/app/src/ui/routes/flows/_flows.root.tsx +++ b/app/src/ui/routes/flows/_flows.root.tsx @@ -1,5 +1,5 @@ import { IconHierarchy2 } from "@tabler/icons-react"; -import { isDebug } from "core"; +import { isDebug } from "core/env"; import { TbSettings } from "react-icons/tb"; import { useBknd } from "../../client/BkndProvider"; import { IconButton } from "../../components/buttons/IconButton"; diff --git a/app/src/ui/routes/flows/components/FlowCreateModal.tsx b/app/src/ui/routes/flows/components/FlowCreateModal.tsx index 6ee8795d..955e2157 100644 --- a/app/src/ui/routes/flows/components/FlowCreateModal.tsx +++ b/app/src/ui/routes/flows/components/FlowCreateModal.tsx @@ -13,7 +13,7 @@ import { ModalTitle, } from "../../../components/modal/Modal2"; import { Step, Steps, useStepContext } from "../../../components/steps/Steps"; -import { s, stringIdentifier } from "core/object/schema"; +import { s, stringIdentifier } from "bknd/utils"; import { standardSchemaResolver } from "@hookform/resolvers/standard-schema"; export type TCreateFlowModalSchema = any; diff --git a/app/src/ui/routes/media/media.settings.tsx b/app/src/ui/routes/media/media.settings.tsx index e8a7cd7e..3a904e26 100644 --- a/app/src/ui/routes/media/media.settings.tsx +++ b/app/src/ui/routes/media/media.settings.tsx @@ -1,5 +1,5 @@ import { IconBrandAws, IconBrandCloudflare, IconCloud, IconServer } from "@tabler/icons-react"; -import { isDebug } from "core"; +import { isDebug } from "core/env"; import { autoFormatString } from "core/utils"; import { twMerge } from "tailwind-merge"; import { useBknd } from "ui/client/BkndProvider"; diff --git a/app/src/ui/routes/settings/components/Setting.tsx b/app/src/ui/routes/settings/components/Setting.tsx index d3078f82..7dcccc35 100644 --- a/app/src/ui/routes/settings/components/Setting.tsx +++ b/app/src/ui/routes/settings/components/Setting.tsx @@ -1,5 +1,5 @@ import { useHotkeys } from "@mantine/hooks"; -import { ucFirst } from "core/utils"; +import { ucFirst, type s } from "bknd/utils"; import { omit } from "lodash-es"; import { type ReactNode, useMemo, useRef, useState } from "react"; import { TbSettings } from "react-icons/tb"; @@ -18,7 +18,6 @@ import { Link, Route, useLocation } from "wouter"; import { extractSchema } from "../utils/schema"; import { SettingNewModal, type SettingsNewModalProps } from "./SettingNewModal"; import { SettingSchemaModal, type SettingsSchemaModalRef } from "./SettingSchemaModal"; -import type { s } from "core/object/schema"; export type SettingProps< Schema extends s.ObjectSchema = s.ObjectSchema, diff --git a/app/src/ui/routes/settings/components/SettingNewModal.tsx b/app/src/ui/routes/settings/components/SettingNewModal.tsx index 2d4e6f83..07cf9227 100644 --- a/app/src/ui/routes/settings/components/SettingNewModal.tsx +++ b/app/src/ui/routes/settings/components/SettingNewModal.tsx @@ -8,7 +8,7 @@ import { JsonSchemaForm, type JsonSchemaFormRef } from "ui/components/form/json- import { Dropdown } from "ui/components/overlay/Dropdown"; import { Modal } from "ui/components/overlay/Modal"; import { useLocation } from "wouter"; -import type { s } from "core/object/schema"; +import type { s } from "bknd/utils"; export type SettingsNewModalProps = { schema: s.ObjectSchema; diff --git a/app/src/ui/routes/settings/utils/schema.ts b/app/src/ui/routes/settings/utils/schema.ts index a6de26b6..1c5e88dc 100644 --- a/app/src/ui/routes/settings/utils/schema.ts +++ b/app/src/ui/routes/settings/utils/schema.ts @@ -1,6 +1,5 @@ import type { JSONSchema7 } from "json-schema"; -import type { s } from "core/object/schema"; -import { omitKeys } from "core/utils"; +import { omitKeys, type s } from "bknd/utils"; export function extractSchema< Schema extends s.ObjectSchema, 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 a05b8f78..0aacc7cc 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 @@ -3,7 +3,7 @@ import { useState } from "react"; import { JsonViewer } from "../../../components/code/JsonViewer"; import { JsonSchemaForm } from "../../../components/form/json-schema"; import { Scrollable } from "../../../layouts/AppShell/AppShell"; -import { parse } from "core/object/schema"; +import { parse } from "bknd/utils"; export default function FlowCreateSchemaTest() { //const schema = flowsConfigSchema; diff --git a/app/src/ui/routes/test/tests/react-hook-errors.tsx b/app/src/ui/routes/test/tests/react-hook-errors.tsx index 08212998..774f4ded 100644 --- a/app/src/ui/routes/test/tests/react-hook-errors.tsx +++ b/app/src/ui/routes/test/tests/react-hook-errors.tsx @@ -1,7 +1,7 @@ import { standardSchemaResolver } from "@hookform/resolvers/standard-schema"; import { TextInput } from "@mantine/core"; import { useForm } from "react-hook-form"; -import { s } from "core/object/schema"; +import { s } from "bknd/utils"; const schema = s.object({ example: s.string().optional(), diff --git a/bun.lock b/bun.lock index a002cc6e..d88b74cb 100644 --- a/bun.lock +++ b/bun.lock @@ -15,7 +15,7 @@ }, "app": { "name": "bknd", - "version": "0.15.0", + "version": "0.16.0-rc.0", "bin": "./dist/cli/index.js", "dependencies": { "@cfworker/json-schema": "^4.1.1", @@ -32,8 +32,7 @@ "bcryptjs": "^3.0.2", "dayjs": "^1.11.13", "fast-xml-parser": "^5.0.8", - "hono": "^4.7.11", - "json-schema-form-react": "^0.0.2", + "hono": "4.8.3", "json-schema-library": "10.0.0-rc7", "json-schema-to-ts": "^3.1.1", "kysely": "^0.27.6", @@ -72,7 +71,7 @@ "dotenv": "^16.4.7", "jotai": "^2.12.2", "jsdom": "^26.0.0", - "jsonv-ts": "^0.2.2", + "jsonv-ts": "^0.3.2", "kysely-d1": "^0.3.0", "kysely-generic-sqlite": "^1.2.1", "libsql-stateless-easy": "^1.8.0", @@ -124,7 +123,6 @@ "version": "0.5.1", "devDependencies": { "@types/bun": "latest", - "bknd": "workspace:*", "tsdx": "^0.14.1", "typescript": "^5.0.0", }, @@ -1218,7 +1216,7 @@ "@types/babel__traverse": ["@types/babel__traverse@7.20.6", "", { "dependencies": { "@babel/types": "^7.20.7" } }, "sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg=="], - "@types/bun": ["@types/bun@1.2.18", "", { "dependencies": { "bun-types": "1.2.18" } }, "sha512-Xf6RaWVheyemaThV0kUfaAUvCNokFr+bH8Jxp+tTZfx7dAPA8z9ePnP9S9+Vspzuxxx9JRAXhnyccRj3GyCMdQ=="], + "@types/bun": ["@types/bun@1.2.19", "", { "dependencies": { "bun-types": "1.2.19" } }, "sha512-d9ZCmrH3CJ2uYKXQIUuZ/pUnTqIvLDS0SK7pFmbx8ma+ziH/FRMoAq5bYpRG7y+w1gl+HgyNZbtqgMq4W4e2Lg=="], "@types/cookie": ["@types/cookie@0.6.0", "", {}, "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA=="], @@ -2208,7 +2206,7 @@ "headers-polyfill": ["headers-polyfill@4.0.3", "", {}, "sha512-IScLbePpkvO846sIwOtOTDjutRMWdXdJmXdMvk6gCBHxFO8d+QKOQedyZSxFTTFYRSmlgSTDtXqqq4pcenBXLQ=="], - "hono": ["hono@4.7.11", "", {}, "sha512-rv0JMwC0KALbbmwJDEnxvQCeJh+xbS3KEWW5PC9cMJ08Ur9xgatI0HmtgYZfOdOSOeYsp5LO2cOhdI8cLEbDEQ=="], + "hono": ["hono@4.8.3", "", {}, "sha512-jYZ6ZtfWjzBdh8H/0CIFfCBHaFL75k+KMzaM177hrWWm2TWL39YMYaJgB74uK/niRc866NMlH9B8uCvIo284WQ=="], "hosted-git-info": ["hosted-git-info@2.8.9", "", {}, "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw=="], @@ -2478,8 +2476,6 @@ "json-schema-compare": ["json-schema-compare@0.2.2", "", { "dependencies": { "lodash": "^4.17.4" } }, "sha512-c4WYmDKyJXhs7WWvAWm3uIYnfyWFoIp+JEoX34rctVvEkMYCPGhXtvmFFXiffBbxfZsvQ0RNnV5H7GvDF5HCqQ=="], - "json-schema-form-react": ["json-schema-form-react@0.0.2", "", { "peerDependencies": { "react": ">=18", "react-dom": ">=18" } }, "sha512-AoLuYpmKmFitc5eZPXKnPmVpel1VVhMTKBz/U5/esLrd74P3uxN2JNVKI/AI2lwLq6K0u9PBqX2pmpVGcTwzaw=="], - "json-schema-library": ["json-schema-library@10.0.0-rc7", "", { "dependencies": { "@sagold/json-pointer": "^6.0.1", "@sagold/json-query": "^6.2.0", "deepmerge": "^4.3.1", "fast-copy": "^3.0.2", "fast-deep-equal": "^3.1.3", "smtp-address-parser": "1.0.10", "valid-url": "^1.0.9" } }, "sha512-q9DMhftVyO8Xa8cfupS5Kx5Uv1A9OJvyRn8DVDMATQv8bITq18cdZimWilwjHIuf2Mzphy67bSJU9gEFno7BLw=="], "json-schema-merge-allof": ["json-schema-merge-allof@0.8.1", "", { "dependencies": { "compute-lcm": "^1.1.2", "json-schema-compare": "^0.2.2", "lodash": "^4.17.20" } }, "sha512-CTUKmIlPJbsWfzRRnOXz+0MjIqvnleIXwFTzz+t9T86HnYX/Rozria6ZVGLktAU9e+NygNljveP+yxqtQp/Q4w=="], @@ -2500,7 +2496,7 @@ "jsonpointer": ["jsonpointer@5.0.1", "", {}, "sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ=="], - "jsonv-ts": ["jsonv-ts@0.2.2", "", { "optionalDependencies": { "hono": "4.7.11" }, "peerDependencies": { "typescript": "^5.0.0" } }, "sha512-g4kUDYYPBb32Nfbv3R55Se6G4CAv4UOXOPJIEPqw8XX+0SSy44T/AREkwFfGz9GwYuX9UxcvOILbgac2nshL4A=="], + "jsonv-ts": ["jsonv-ts@0.3.2", "", { "optionalDependencies": { "hono": "*" }, "peerDependencies": { "typescript": "^5.0.0" } }, "sha512-wGKLo0naUzgOCa2BgtlKZlF47po7hPjGXqDZK2lOoJ/4sE1lb4fMvf0YJrRghqfwg9QNtWz01xALr+F0QECYag=="], "jsonwebtoken": ["jsonwebtoken@9.0.2", "", { "dependencies": { "jws": "^3.2.2", "lodash.includes": "^4.3.0", "lodash.isboolean": "^3.0.3", "lodash.isinteger": "^4.0.4", "lodash.isnumber": "^3.0.3", "lodash.isplainobject": "^4.0.6", "lodash.isstring": "^4.0.1", "lodash.once": "^4.0.0", "ms": "^2.1.1", "semver": "^7.5.4" } }, "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ=="], @@ -4024,7 +4020,7 @@ "@testing-library/jest-dom/chalk": ["chalk@3.0.0", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg=="], - "@types/bun/bun-types": ["bun-types@1.2.18", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-04+Eha5NP7Z0A9YgDAzMk5PHR16ZuLVa83b26kH5+cp1qZW4F6FmAURngE7INf4tKOvCE69vYvDEwoNl1tGiWw=="], + "@types/bun/bun-types": ["bun-types@1.2.19", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-uAOTaZSPuYsWIXRpj7o56Let0g/wjihKCkeRqUBhlLVM/Bt+Fj9xTo+LhC1OV1XDaGkz4hNC80et5xgy+9KTHQ=="], "@types/pg/pg-types": ["pg-types@4.0.2", "", { "dependencies": { "pg-int8": "1.0.1", "pg-numeric": "1.0.2", "postgres-array": "~3.0.1", "postgres-bytea": "~3.0.0", "postgres-date": "~2.1.0", "postgres-interval": "^3.0.0", "postgres-range": "^1.1.1" } }, "sha512-cRL3JpS3lKMGsKaWndugWQoLOCoP+Cic8oseVcbr0qhPzYD5DWXK+RZ9LY9wxRf7RQia4SCwQlXk0q6FCPrVng=="], diff --git a/examples/deno/main.ts b/examples/deno/main.ts new file mode 100644 index 00000000..58e052f2 --- /dev/null +++ b/examples/deno/main.ts @@ -0,0 +1,14 @@ +import { createRuntimeApp } from "bknd/adapter"; + +const app = await createRuntimeApp({ + connection: { + url: "file:./data.db", + }, + adminOptions: { + // currently needs a hosted version of the static assets + assetsPath: "https://cdn.bknd.io/bknd/static/0.15.0-rc.9/", + }, +}); + +// @ts-ignore +Deno.serve(app.fetch); diff --git a/examples/deno/package.json b/examples/deno/package.json new file mode 100644 index 00000000..97faf720 --- /dev/null +++ b/examples/deno/package.json @@ -0,0 +1,7 @@ +{ + "name": "bknd-deno-example", + "private": true, + "dependencies": { + "bknd": "file:../../app" + } +} diff --git a/examples/waku/.gitignore b/examples/waku/.gitignore new file mode 100644 index 00000000..ad583432 --- /dev/null +++ b/examples/waku/.gitignore @@ -0,0 +1,7 @@ +node_modules +dist +.env* +*.tsbuildinfo +.cache +.DS_Store +*.pem diff --git a/examples/waku/bknd.config.ts b/examples/waku/bknd.config.ts new file mode 100644 index 00000000..28ecf039 --- /dev/null +++ b/examples/waku/bknd.config.ts @@ -0,0 +1,62 @@ +import { registerLocalMediaAdapter } from "bknd/adapter/node"; +import type { BkndConfig } from "bknd/adapter"; +import { boolean, em, entity, text } from "bknd/data"; +import { secureRandomString } from "bknd/utils"; + +// since we're running in node, we can register the local media adapter +const local = registerLocalMediaAdapter(); + +const schema = em({ + todos: entity("todos", { + title: text(), + done: boolean(), + }), +}); + +// register your schema to get automatic type completion +type Database = (typeof schema)["DB"]; +declare module "bknd/core" { + interface DB extends Database {} +} + +export default { + // we can use any libsql config, and if omitted, uses in-memory + connection: { + url: process.env.DB_URL ?? "file:data.db", + }, + // an initial config is only applied if the database is empty + initialConfig: { + data: schema.toJSON(), + // we're enabling auth ... + auth: { + enabled: true, + jwt: { + issuer: "bknd-waku-example", + secret: secureRandomString(64), + }, + }, + // ... and media + media: { + enabled: true, + adapter: local({ + path: "./public/uploads", + }), + }, + }, + options: { + // the seed option is only executed if the database was empty + seed: async (ctx) => { + // create some entries + await ctx.em.mutator("todos").insertMany([ + { title: "Learn bknd", done: true }, + { title: "Build something cool", done: false }, + ]); + + // and create a user + await ctx.app.module.auth.createUser({ + email: "test@bknd.io", + password: "12345678", + }); + }, + }, +} as const satisfies BkndConfig<{ DB_URL?: string }>; diff --git a/examples/waku/package.json b/examples/waku/package.json new file mode 100644 index 00000000..1ce31e49 --- /dev/null +++ b/examples/waku/package.json @@ -0,0 +1,26 @@ +{ + "name": "waku", + "version": "0.0.0", + "type": "module", + "private": true, + "scripts": { + "dev": "waku dev", + "build": "waku build", + "start": "waku start" + }, + "dependencies": { + "react": "19.0.0", + "react-dom": "19.0.0", + "react-server-dom-webpack": "19.0.0", + "waku": "0.23.3", + "bknd": "file:../../app" + }, + "devDependencies": { + "@tailwindcss/postcss": "4.1.10", + "@types/react": "19.1.8", + "@types/react-dom": "19.1.6", + "postcss": "8.5.6", + "tailwindcss": "4.1.10", + "typescript": "5.8.3" + } +} diff --git a/examples/waku/postcss.config.js b/examples/waku/postcss.config.js new file mode 100644 index 00000000..a34a3d56 --- /dev/null +++ b/examples/waku/postcss.config.js @@ -0,0 +1,5 @@ +export default { + plugins: { + '@tailwindcss/postcss': {}, + }, +}; diff --git a/examples/waku/public/images/favicon.png b/examples/waku/public/images/favicon.png new file mode 100644 index 00000000..cd90d790 Binary files /dev/null and b/examples/waku/public/images/favicon.png differ diff --git a/examples/waku/public/robots.txt b/examples/waku/public/robots.txt new file mode 100644 index 00000000..b4d27bb5 --- /dev/null +++ b/examples/waku/public/robots.txt @@ -0,0 +1,2 @@ +User-agent: * +Disallow: /RSC/ \ No newline at end of file diff --git a/examples/waku/src/bknd.ts b/examples/waku/src/bknd.ts new file mode 100644 index 00000000..577d4b22 --- /dev/null +++ b/examples/waku/src/bknd.ts @@ -0,0 +1,22 @@ +import { createFrameworkApp } from "bknd/adapter"; +import config from "../bknd.config"; + +export async function getApp() { + return await createFrameworkApp(config, process.env, { + force: import.meta.env && !import.meta.env.PROD, + }); +} + +export async function getApi(opts?: { + headers?: Headers | any; + verify?: boolean; +}) { + const app = await getApp(); + if (opts?.verify && opts.headers) { + const api = app.getApi({ headers: opts.headers }); + await api.verifyAuth(); + return api; + } + + return app.getApi(); +} diff --git a/examples/waku/src/components/counter.tsx b/examples/waku/src/components/counter.tsx new file mode 100644 index 00000000..0e540b8b --- /dev/null +++ b/examples/waku/src/components/counter.tsx @@ -0,0 +1,21 @@ +'use client'; + +import { useState } from 'react'; + +export const Counter = () => { + const [count, setCount] = useState(0); + + const handleIncrement = () => setCount((c) => c + 1); + + return ( +
+
Count: {count}
+ +
+ ); +}; diff --git a/examples/waku/src/components/footer.tsx b/examples/waku/src/components/footer.tsx new file mode 100644 index 00000000..8cfd9c89 --- /dev/null +++ b/examples/waku/src/components/footer.tsx @@ -0,0 +1,18 @@ +export const Footer = () => { + return ( + + ); +}; diff --git a/examples/waku/src/components/header.tsx b/examples/waku/src/components/header.tsx new file mode 100644 index 00000000..1b03ba54 --- /dev/null +++ b/examples/waku/src/components/header.tsx @@ -0,0 +1,11 @@ +import { Link } from 'waku'; + +export const Header = () => { + return ( +
+

+ Waku starter +

+
+ ); +}; diff --git a/examples/waku/src/lib/waku/client.tsx b/examples/waku/src/lib/waku/client.tsx new file mode 100644 index 00000000..2d889c97 --- /dev/null +++ b/examples/waku/src/lib/waku/client.tsx @@ -0,0 +1,20 @@ +"use client"; + +import * as React from "react"; + +export function BrowserOnly(props: React.SuspenseProps) { + const hydrated = useHydrated(); + if (!hydrated) { + return props.fallback; + } + return ; +} + +const noopStore = () => () => {}; + +const useHydrated = () => + React.useSyncExternalStore( + noopStore, + () => true, + () => false + ); diff --git a/examples/waku/src/lib/waku/server.ts b/examples/waku/src/lib/waku/server.ts new file mode 100644 index 00000000..608aa26e --- /dev/null +++ b/examples/waku/src/lib/waku/server.ts @@ -0,0 +1,26 @@ +"use server"; + +import { getContext } from "waku/middleware/context"; +import { getApi } from "../../bknd"; + +export { unstable_rerenderRoute as rerender } from "waku/router/server"; + +export function context() { + return getContext(); +} + +export function handlerReq() { + return getContext().req; +} + +export function headers() { + const context = getContext(); + return new Headers(context.req.headers); +} + +export async function getUserApi(opts?: { verify?: boolean }) { + return await getApi({ + headers: headers(), + verify: !!opts?.verify, + }); +} diff --git a/examples/waku/src/pages.gen.ts b/examples/waku/src/pages.gen.ts new file mode 100644 index 00000000..a3c9fb7f --- /dev/null +++ b/examples/waku/src/pages.gen.ts @@ -0,0 +1,30 @@ +// deno-fmt-ignore-file +// biome-ignore format: generated types do not need formatting +// prettier-ignore +import type { PathsForPages, GetConfigResponse } from 'waku/router'; + +// prettier-ignore +import type { getConfig as File_About_getConfig } from './pages/about'; +// prettier-ignore +import type { getConfig as File_AdminAdmin_getConfig } from './pages/admin/[...admin]'; +// prettier-ignore +import type { getConfig as File_Index_getConfig } from './pages/index'; + +// prettier-ignore +type Page = +| ({ path: '/about' } & GetConfigResponse) +| ({ path: '/admin/[...admin]' } & GetConfigResponse) +| { path: '/admin'; render: 'dynamic' } +| ({ path: '/' } & GetConfigResponse) +| { path: '/login'; render: 'dynamic' } +| { path: '/test'; render: 'dynamic' }; + +// prettier-ignore +declare module 'waku/router' { + interface RouteConfig { + paths: PathsForPages; + } + interface CreatePagesConfig { + pages: Page; + } +} diff --git a/examples/waku/src/pages/_layout.tsx b/examples/waku/src/pages/_layout.tsx new file mode 100644 index 00000000..2e3c7e93 --- /dev/null +++ b/examples/waku/src/pages/_layout.tsx @@ -0,0 +1,28 @@ +import "../styles.css"; + +import type { ReactNode } from "react"; + +import { ClientProvider } from "bknd/client"; + +type RootLayoutProps = { children: ReactNode }; + +export default async function RootLayout({ children }: RootLayoutProps) { + const data = await getData(); + + return children; +} + +const getData = async () => { + const data = { + description: "An internet website!", + icon: "/images/favicon.png", + }; + + return data; +}; + +export const getConfig = async () => { + return { + render: "static", + } as const; +}; diff --git a/examples/waku/src/pages/about.tsx b/examples/waku/src/pages/about.tsx new file mode 100644 index 00000000..15d4c90e --- /dev/null +++ b/examples/waku/src/pages/about.tsx @@ -0,0 +1,32 @@ +import { Link } from 'waku'; + +export default async function AboutPage() { + const data = await getData(); + + return ( +
+ {data.title} +

{data.headline}

+

{data.body}

+ + Return home + +
+ ); +} + +const getData = async () => { + const data = { + title: 'About', + headline: 'About Waku', + body: 'The minimal React framework', + }; + + return data; +}; + +export const getConfig = async () => { + return { + render: 'static', + } as const; +}; diff --git a/examples/waku/src/pages/admin/[...admin].tsx b/examples/waku/src/pages/admin/[...admin].tsx new file mode 100644 index 00000000..136182bb --- /dev/null +++ b/examples/waku/src/pages/admin/[...admin].tsx @@ -0,0 +1,32 @@ +/** + * See https://github.com/wakujs/waku/issues/1499 + */ + +import { Suspense, lazy } from "react"; +import { getUserApi } from "../../lib/waku/server"; + +const AdminComponent = lazy(() => import("./_components/AdminLoader")); + +export default async function HomePage() { + const api = await getUserApi({ verify: true }); + + // @ts-ignore + const styles = await import("bknd/dist/styles.css?inline").then((m) => m.default); + return ( + <> + + + + + + ); +} + +// Enable dynamic server rendering. +// Static rendering is possible if you want to render at build time. +// The Hono context will not be available. +export const getConfig = async () => { + return { + render: "dynamic", + } as const; +}; diff --git a/examples/waku/src/pages/admin/_components/AdminImpl.tsx b/examples/waku/src/pages/admin/_components/AdminImpl.tsx new file mode 100644 index 00000000..e1c9afd0 --- /dev/null +++ b/examples/waku/src/pages/admin/_components/AdminImpl.tsx @@ -0,0 +1,23 @@ +"use client"; + +import { Admin, type BkndAdminProps } from "bknd/ui"; + +export const AdminImpl = (props: BkndAdminProps) => { + if (typeof window === "undefined") { + return null; + } + + return ( + + ); +}; + +export default AdminImpl; diff --git a/examples/waku/src/pages/admin/_components/AdminLoader.tsx b/examples/waku/src/pages/admin/_components/AdminLoader.tsx new file mode 100644 index 00000000..6b2e9a74 --- /dev/null +++ b/examples/waku/src/pages/admin/_components/AdminLoader.tsx @@ -0,0 +1,18 @@ +"use client"; + +import type { BkndAdminProps } from "bknd/ui"; +import { lazy } from "react"; +import { BrowserOnly } from "../../../lib/waku/client"; + +const AdminImpl = import.meta.env.SSR ? undefined : lazy(() => import("./AdminImpl")); + +export const AdminLoader = (props: BkndAdminProps) => { + return ( + + {/* @ts-expect-error */} + + + ); +}; + +export default AdminLoader; diff --git a/examples/waku/src/pages/admin/index.tsx b/examples/waku/src/pages/admin/index.tsx new file mode 100644 index 00000000..7d32f6fe --- /dev/null +++ b/examples/waku/src/pages/admin/index.tsx @@ -0,0 +1,6 @@ +import { unstable_redirect as redirect } from "waku/router/server"; + +export default async function AdminIndex() { + await new Promise((resolve) => setTimeout(resolve, 100)); + redirect("/admin/data"); +} diff --git a/examples/waku/src/pages/api/[...api].ts b/examples/waku/src/pages/api/[...api].ts new file mode 100644 index 00000000..eaf8bb35 --- /dev/null +++ b/examples/waku/src/pages/api/[...api].ts @@ -0,0 +1,5 @@ +import { getApp } from "../../bknd"; + +export default async function handler(request: Request) { + return (await getApp()).fetch(request); +} diff --git a/examples/waku/src/pages/index.tsx b/examples/waku/src/pages/index.tsx new file mode 100644 index 00000000..553d23bc --- /dev/null +++ b/examples/waku/src/pages/index.tsx @@ -0,0 +1,70 @@ +import { Link } from "waku"; + +import { Counter } from "../components/counter"; +import { rerender, getUserApi } from "../lib/waku/server"; + +async function toggleTodo(todo: any, path: string) { + "use server"; + const api = await getUserApi(); + await api.data.updateOne("todos", todo.id, { + done: !todo.done, + }); + rerender(path); +} + +export default async function HomePage({ path }: any) { + const api = await getUserApi({ verify: true }); + const todos = await api.data.readMany("todos"); + const user = api.getUser(); + const data = await getData(); + + return ( +
+ {data.title} +

{data.headline}

+

{data.body}

+
    + {todos?.map((todo) => ( +
  • + {todo.title} {todo.done ? "✅" : "❌"} + + +
  • + + ))} +
+ + + About page + + {user ? ( + + Logout ({user.email}) + + ) : ( + + Login + + )} + + Admin + +
+ ); +} + +const getData = async () => { + const data = { + title: "Waku", + headline: "Waku", + body: "Hello world!", + }; + + return data; +}; + +export const getConfig = async () => { + return { + render: "dynamic", + } as const; +}; diff --git a/examples/waku/src/pages/login.tsx b/examples/waku/src/pages/login.tsx new file mode 100644 index 00000000..db41ac0a --- /dev/null +++ b/examples/waku/src/pages/login.tsx @@ -0,0 +1,9 @@ +export default function LoginPage() { + return ( +
+ + + +
+ ); +} diff --git a/examples/waku/src/pages/test.tsx b/examples/waku/src/pages/test.tsx new file mode 100644 index 00000000..d74ce923 --- /dev/null +++ b/examples/waku/src/pages/test.tsx @@ -0,0 +1,58 @@ +"use client"; + +import { useEffect } from "react"; + +export default function Test() { + useEffect(() => { + bridge(); + }, []); + return null; +} + +async function bridge() { + const aud = new URLSearchParams(location.search).get("aud") || ""; + // 1. Verify the user still has an auth cookie + const me = await fetch("/api/auth/me", { credentials: "include" }); + console.log("sso-bridge:me", me); + if (!me.ok) { + console.log("sso-bridge:no session"); + parent.postMessage({ type: "NOSESSION" }, aud); + } else { + console.log("sso-bridge:session"); + + // 2. Get short-lived JWT (internal endpoint, same origin) + const res = await fetch("/api/issue-jwt", { + credentials: "include", + headers: { "Content-Type": "application/json" }, + method: "POST", + body: JSON.stringify({ + aud, + }), + }); + console.log("sso-bridge:res", res); + const { jwt, exp } = (await res.json()) as any; // exp = unix timestamp seconds + console.log("sso-bridge:jwt", { jwt, exp }); + + // 3. Send token up + parent.postMessage({ type: "JWT", jwt, exp }, aud); + + // 4. Listen for refresh requests + window.addEventListener("message", async (ev) => { + console.log("sso-bridge:message", ev); + if (ev.origin !== aud) return; + if (ev.data !== "REFRESH") return; + console.log("sso-bridge:message:refresh"); + + const r = await fetch("/api/issue-jwt", { + credentials: "include", + method: "POST", + headers: { "content-type": "application/json" }, + body: JSON.stringify({ aud: ev.origin }), + }); + console.log("sso-bridge:message:r", r); + const { jwt, exp } = (await r.json()) as any; + console.log("sso-bridge:message:jwt", { jwt, exp }); + parent.postMessage({ type: "JWT", jwt, exp }, ev.origin); + }); + } +} diff --git a/examples/waku/src/styles.css b/examples/waku/src/styles.css new file mode 100644 index 00000000..f1d3c37c --- /dev/null +++ b/examples/waku/src/styles.css @@ -0,0 +1,3 @@ +@import url('https://fonts.googleapis.com/css2?family=Nunito:ital,wght@0,400;0,700;1,400;1,700&display=swap') +layer(base); +@import 'tailwindcss'; diff --git a/examples/waku/tsconfig.json b/examples/waku/tsconfig.json new file mode 100644 index 00000000..3b26664b --- /dev/null +++ b/examples/waku/tsconfig.json @@ -0,0 +1,18 @@ +{ + "include": ["**/*.ts", "**/*.tsx"], + "compilerOptions": { + "strict": true, + "target": "esnext", + "noEmit": true, + "isolatedModules": true, + "moduleDetection": "force", + "downlevelIteration": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "bundler", + "skipLibCheck": true, + "noUncheckedIndexedAccess": true, + "exactOptionalPropertyTypes": true, + "jsx": "react-jsx" + } +}