mirror of
https://github.com/bknd-io/bknd/
synced 2026-08-02 08:06:00 +00:00
Compare commits
27 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 076f82b933 | |||
| 324e872849 | |||
| 64844c1aa6 | |||
| bebf9e0411 | |||
| ca55503e66 | |||
| 1ec3ba0e92 | |||
| ea372a8125 | |||
| 847c264b35 | |||
| b4f024bef8 | |||
| 8bc63ba2a7 | |||
| cecb0e829a | |||
| 491bd65da1 | |||
| ba7fea5571 | |||
| ba7587aa3f | |||
| 0a49061549 | |||
| 682ecc6dac | |||
| efbc5f115f | |||
| b4d6b1c04d | |||
| e21f1a3ee2 | |||
| a9efee3555 | |||
| 2ca1f64ae6 | |||
| 2b32a836cd | |||
| 9d5bc61f33 | |||
| ea46dc8df0 | |||
| 88ec3432a0 | |||
| ddd6ffe8c6 | |||
| 43c8f14470 |
@@ -20,7 +20,7 @@ jobs:
|
|||||||
- name: Setup Bun
|
- name: Setup Bun
|
||||||
uses: oven-sh/setup-bun@v1
|
uses: oven-sh/setup-bun@v1
|
||||||
with:
|
with:
|
||||||
bun-version: "1.2.14"
|
bun-version: "1.2.19"
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
working-directory: ./app
|
working-directory: ./app
|
||||||
|
|||||||
@@ -1 +1,2 @@
|
|||||||
tmp/*
|
tmp/*
|
||||||
|
!tmp/.gitkeep
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
import { afterAll, beforeAll, describe, expect, it } from "bun:test";
|
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 { DataApi } from "../../src/data/api/DataApi";
|
||||||
import { DataController } from "../../src/data/api/DataController";
|
import { DataController } from "../../src/data/api/DataController";
|
||||||
import { dataConfigSchema } from "../../src/data/data-schema";
|
import { dataConfigSchema } from "../../src/data/data-schema";
|
||||||
import * as proto from "../../src/data/prototype";
|
import * as proto from "../../src/data/prototype";
|
||||||
import { schemaToEm } from "../helper";
|
import { schemaToEm } from "../helper";
|
||||||
import { disableConsoleLog, enableConsoleLog } from "core/utils/test";
|
import { disableConsoleLog, enableConsoleLog } from "core/utils/test";
|
||||||
import { parse } from "core/object/schema";
|
import { parse } from "core/utils/schema";
|
||||||
|
|
||||||
beforeAll(disableConsoleLog);
|
beforeAll(disableConsoleLog);
|
||||||
afterAll(enableConsoleLog);
|
afterAll(enableConsoleLog);
|
||||||
@@ -202,7 +202,7 @@ describe("DataApi", () => {
|
|||||||
{
|
{
|
||||||
// create many
|
// create many
|
||||||
const res = await api.createMany("posts", payload);
|
const res = await api.createMany("posts", payload);
|
||||||
expect(res.data.length).toEqual(4);
|
expect(res.data?.length).toEqual(4);
|
||||||
expect(res.ok).toBeTrue();
|
expect(res.ok).toBeTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ describe("AppServer", () => {
|
|||||||
expect(server).toBeDefined();
|
expect(server).toBeDefined();
|
||||||
expect(server.config).toEqual({
|
expect(server.config).toEqual({
|
||||||
cors: {
|
cors: {
|
||||||
|
allow_credentials: true,
|
||||||
origin: "*",
|
origin: "*",
|
||||||
allow_methods: ["GET", "POST", "PATCH", "PUT", "DELETE"],
|
allow_methods: ["GET", "POST", "PATCH", "PUT", "DELETE"],
|
||||||
allow_headers: ["Content-Type", "Content-Length", "Authorization", "Accept"],
|
allow_headers: ["Content-Type", "Content-Length", "Authorization", "Accept"],
|
||||||
@@ -25,6 +26,7 @@ describe("AppServer", () => {
|
|||||||
expect(server).toBeDefined();
|
expect(server).toBeDefined();
|
||||||
expect(server.config).toEqual({
|
expect(server.config).toEqual({
|
||||||
cors: {
|
cors: {
|
||||||
|
allow_credentials: true,
|
||||||
origin: "https",
|
origin: "https",
|
||||||
allow_methods: ["GET", "POST"],
|
allow_methods: ["GET", "POST"],
|
||||||
allow_headers: ["Content-Type", "Content-Length", "Authorization", "Accept"],
|
allow_headers: ["Content-Type", "Content-Length", "Authorization", "Accept"],
|
||||||
|
|||||||
@@ -1,45 +1,3 @@
|
|||||||
import { describe, expect, test } from "bun:test";
|
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 {
|
describe("Authenticator", async () => {});
|
||||||
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<User, "email" | "password">) {
|
|
||||||
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<any>(token);
|
|
||||||
expect(decoded).toEqual({ id: 1, email: "d", username: "test" });
|
|
||||||
expect(await auth.verify(token)).toBe(true);
|
|
||||||
});*/
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { describe, expect, test } from "bun:test";
|
import { describe, expect, test } from "bun:test";
|
||||||
import { Guard } from "../../../src/auth";
|
import { Guard } from "../../../src/auth/authorize/Guard";
|
||||||
|
|
||||||
describe("authorize", () => {
|
describe("authorize", () => {
|
||||||
test("basic", async () => {
|
test("basic", async () => {
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
import { AuthStrategy, registries, type Authenticator } from "bknd";
|
||||||
|
import { describe, it, expect } from "bun:test";
|
||||||
|
import { createApp } from "core/test/utils";
|
||||||
|
import { s } from "bknd/utils";
|
||||||
|
import { Hono } from "hono";
|
||||||
|
|
||||||
|
const customStrategySchema = s.object({
|
||||||
|
name: s.string(),
|
||||||
|
});
|
||||||
|
type CustomStrategyOptions = s.Static<typeof customStrategySchema>;
|
||||||
|
|
||||||
|
class CustomStrategy extends AuthStrategy<typeof customStrategySchema> {
|
||||||
|
constructor(config: Partial<CustomStrategyOptions> = {}) {
|
||||||
|
super(config as any, "custom", "custom", "form");
|
||||||
|
}
|
||||||
|
|
||||||
|
getSchema() {
|
||||||
|
return customStrategySchema;
|
||||||
|
}
|
||||||
|
|
||||||
|
getController(authenticator: Authenticator) {
|
||||||
|
return new Hono()
|
||||||
|
.post("/login", async (c) => {
|
||||||
|
try {
|
||||||
|
// do magic, and it succeeds, resolve login
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
return await authenticator.resolveLogin();
|
||||||
|
} catch (e) {
|
||||||
|
// in case of error, respond with the error
|
||||||
|
return authenticator.respondWithError(c, e as any);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.post("/register", async (c) => {
|
||||||
|
try {
|
||||||
|
// do magic, and it succeeds, resolve register
|
||||||
|
// @ts-ignore
|
||||||
|
return await authenticator.resolveRegister();
|
||||||
|
} catch (e) {
|
||||||
|
// in case of error, respond with the error
|
||||||
|
return authenticator.respondWithError(c, e as any);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("custom auth strategy", () => {
|
||||||
|
it("should be registered", async () => {
|
||||||
|
registries.auth.register("custom", CustomStrategy);
|
||||||
|
|
||||||
|
const app = createApp({
|
||||||
|
initialConfig: {
|
||||||
|
auth: {
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await app.build();
|
||||||
|
|
||||||
|
const strategies = app
|
||||||
|
.getSchema()
|
||||||
|
.auth.properties.strategies.additionalProperties.anyOf.map((s) => s.properties.type.const);
|
||||||
|
|
||||||
|
expect(strategies).toContain("custom");
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { describe, expect, test } from "bun:test";
|
import { describe, expect, test } from "bun:test";
|
||||||
import { Registry } from "core";
|
import { Registry } from "core/registry/Registry";
|
||||||
import { s } from "core/object/schema";
|
import { s } from "core/utils/schema";
|
||||||
|
|
||||||
type Constructor<T> = new (...args: any[]) => T;
|
type Constructor<T> = new (...args: any[]) => T;
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { describe, expect, test } from "bun:test";
|
import { describe, expect, test } from "bun:test";
|
||||||
import { SchemaObject } from "../../../src/core";
|
import { s } from "core/utils/schema";
|
||||||
import { s } from "core/object/schema";
|
import { SchemaObject } from "core/object/SchemaObject";
|
||||||
|
|
||||||
describe("SchemaObject", async () => {
|
describe("SchemaObject", async () => {
|
||||||
test("basic", async () => {
|
test("basic", async () => {
|
||||||
|
|||||||
@@ -1,19 +1,16 @@
|
|||||||
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
||||||
|
|
||||||
import { Guard } from "../../src/auth";
|
import { Guard } from "../../src/auth/authorize/Guard";
|
||||||
import { parse } from "core/object/schema";
|
import { parse } from "core/utils/schema";
|
||||||
import {
|
|
||||||
Entity,
|
|
||||||
type EntityData,
|
|
||||||
EntityManager,
|
|
||||||
ManyToOneRelation,
|
|
||||||
TextField,
|
|
||||||
} from "../../src/data";
|
|
||||||
import { DataController } from "../../src/data/api/DataController";
|
import { DataController } from "../../src/data/api/DataController";
|
||||||
import { dataConfigSchema } from "../../src/data/data-schema";
|
import { dataConfigSchema } from "../../src/data/data-schema";
|
||||||
import { disableConsoleLog, enableConsoleLog, getDummyConnection } from "../helper";
|
import { disableConsoleLog, enableConsoleLog, getDummyConnection } from "../helper";
|
||||||
import type { RepositoryResultJSON } from "data/entities/query/RepositoryResult";
|
import type { RepositoryResultJSON } from "data/entities/query/RepositoryResult";
|
||||||
import type { MutatorResultJSON } from "data/entities/mutation/MutatorResult";
|
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();
|
const { dummyConnection, afterAllCleanup } = getDummyConnection();
|
||||||
beforeAll(() => disableConsoleLog(["log", "warn"]));
|
beforeAll(() => disableConsoleLog(["log", "warn"]));
|
||||||
|
|||||||
@@ -1,12 +1,6 @@
|
|||||||
import { afterAll, describe, expect, test } from "bun:test";
|
import { afterAll, describe, expect, test } from "bun:test";
|
||||||
import {
|
import { Entity, EntityManager } from "data/entities";
|
||||||
Entity,
|
import { TextField, PrimaryField, NumberField } from "data/fields";
|
||||||
EntityManager,
|
|
||||||
NumberField,
|
|
||||||
PrimaryField,
|
|
||||||
Repository,
|
|
||||||
TextField,
|
|
||||||
} from "../../src/data";
|
|
||||||
import { getDummyConnection } from "./helper";
|
import { getDummyConnection } from "./helper";
|
||||||
|
|
||||||
const { dummyConnection, afterAllCleanup } = getDummyConnection();
|
const { dummyConnection, afterAllCleanup } = getDummyConnection();
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { unlink } from "node:fs/promises";
|
|||||||
import type { SqliteDatabase } from "kysely";
|
import type { SqliteDatabase } from "kysely";
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import Database from "libsql";
|
import Database from "libsql";
|
||||||
import { SqliteLocalConnection } from "../../src/data";
|
import { SqliteLocalConnection } from "data/connection/sqlite/SqliteLocalConnection";
|
||||||
|
|
||||||
export function getDummyDatabase(memory: boolean = true): {
|
export function getDummyDatabase(memory: boolean = true): {
|
||||||
dummyDb: SqliteDatabase;
|
dummyDb: SqliteDatabase;
|
||||||
|
|||||||
@@ -1,13 +1,10 @@
|
|||||||
// eslint-disable-next-line import/no-unresolved
|
// eslint-disable-next-line import/no-unresolved
|
||||||
import { afterAll, describe, expect, test } from "bun:test";
|
import { afterAll, describe, expect, test } from "bun:test";
|
||||||
import {
|
import { Entity } from "data/entities";
|
||||||
Entity,
|
import { EntityManager } from "data/entities/EntityManager";
|
||||||
EntityManager,
|
import { ManyToOneRelation } from "data/relations";
|
||||||
ManyToOneRelation,
|
import { NumberField, TextField } from "data/fields";
|
||||||
NumberField,
|
import { SchemaManager } from "data/schema/SchemaManager";
|
||||||
SchemaManager,
|
|
||||||
TextField,
|
|
||||||
} from "../../src/data";
|
|
||||||
import { getDummyConnection } from "./helper";
|
import { getDummyConnection } from "./helper";
|
||||||
|
|
||||||
const { dummyConnection, afterAllCleanup } = getDummyConnection();
|
const { dummyConnection, afterAllCleanup } = getDummyConnection();
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
// eslint-disable-next-line import/no-unresolved
|
// eslint-disable-next-line import/no-unresolved
|
||||||
import { afterAll, describe, expect, test } from "bun:test";
|
import { afterAll, describe, expect, test } from "bun:test";
|
||||||
import { Entity, EntityManager, Mutator, NumberField, TextField } from "../../src/data";
|
import { Entity, EntityManager } from "data/entities";
|
||||||
import { TransformPersistFailedException } from "../../src/data/errors";
|
import { NumberField, TextField } from "data/fields";
|
||||||
|
import { TransformPersistFailedException } from "data/errors";
|
||||||
import { getDummyConnection } from "./helper";
|
import { getDummyConnection } from "./helper";
|
||||||
|
|
||||||
const { dummyConnection, afterAllCleanup } = getDummyConnection();
|
const { dummyConnection, afterAllCleanup } = getDummyConnection();
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import { afterAll, expect as bunExpect, describe, test } from "bun:test";
|
import { afterAll, expect as bunExpect, describe, test } from "bun:test";
|
||||||
import { stripMark } from "core/object/schema";
|
import { stripMark } from "core/utils/schema";
|
||||||
import { Entity, EntityManager, PolymorphicRelation, TextField } from "../../src/data";
|
import { Entity, EntityManager } from "data/entities";
|
||||||
|
import { TextField } from "data/fields";
|
||||||
|
import { PolymorphicRelation } from "data/relations";
|
||||||
import { getDummyConnection } from "./helper";
|
import { getDummyConnection } from "./helper";
|
||||||
|
|
||||||
const { dummyConnection, afterAllCleanup } = getDummyConnection();
|
const { dummyConnection, afterAllCleanup } = getDummyConnection();
|
||||||
|
|||||||
@@ -2,19 +2,20 @@ import { describe, expect, test } from "bun:test";
|
|||||||
import {
|
import {
|
||||||
BooleanField,
|
BooleanField,
|
||||||
DateField,
|
DateField,
|
||||||
Entity,
|
|
||||||
EntityIndex,
|
|
||||||
EntityManager,
|
|
||||||
EnumField,
|
EnumField,
|
||||||
JsonField,
|
JsonField,
|
||||||
|
NumberField,
|
||||||
|
TextField,
|
||||||
|
EntityIndex,
|
||||||
|
} from "data/fields";
|
||||||
|
import { Entity, EntityManager } from "data/entities";
|
||||||
|
import {
|
||||||
ManyToManyRelation,
|
ManyToManyRelation,
|
||||||
ManyToOneRelation,
|
ManyToOneRelation,
|
||||||
NumberField,
|
|
||||||
OneToOneRelation,
|
OneToOneRelation,
|
||||||
PolymorphicRelation,
|
PolymorphicRelation,
|
||||||
TextField,
|
} from "data/relations";
|
||||||
} from "../../src/data";
|
import { DummyConnection } from "data/connection/DummyConnection";
|
||||||
import { DummyConnection } from "../../src/data/connection/DummyConnection";
|
|
||||||
import {
|
import {
|
||||||
FieldPrototype,
|
FieldPrototype,
|
||||||
type FieldSchema,
|
type FieldSchema,
|
||||||
@@ -32,8 +33,8 @@ import {
|
|||||||
number,
|
number,
|
||||||
relation,
|
relation,
|
||||||
text,
|
text,
|
||||||
} from "../../src/data/prototype";
|
} from "data/prototype";
|
||||||
import { MediaField } from "../../src/media/MediaField";
|
import { MediaField } from "media/MediaField";
|
||||||
|
|
||||||
describe("prototype", () => {
|
describe("prototype", () => {
|
||||||
test("...", () => {
|
test("...", () => {
|
||||||
@@ -296,9 +297,9 @@ describe("prototype", () => {
|
|||||||
new Entity("posts", [new TextField("name"), new TextField("slug", { required: true })]),
|
new Entity("posts", [new TextField("name"), new TextField("slug", { required: true })]),
|
||||||
new Entity("comments", [new TextField("some")]),
|
new Entity("comments", [new TextField("some")]),
|
||||||
new Entity("users", [new TextField("email")]),
|
new Entity("users", [new TextField("email")]),
|
||||||
];
|
] as const;
|
||||||
const _em2 = new EntityManager(
|
const _em2 = new EntityManager(
|
||||||
es,
|
[...es],
|
||||||
new DummyConnection(),
|
new DummyConnection(),
|
||||||
[new ManyToOneRelation(es[0], es[1]), new ManyToOneRelation(es[0], es[2])],
|
[new ManyToOneRelation(es[0], es[1]), new ManyToOneRelation(es[0], es[2])],
|
||||||
[
|
[
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
// eslint-disable-next-line import/no-unresolved
|
// eslint-disable-next-line import/no-unresolved
|
||||||
import { afterAll, describe, expect, test } from "bun:test";
|
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 {
|
import {
|
||||||
ManyToManyRelation,
|
ManyToManyRelation,
|
||||||
ManyToOneRelation,
|
ManyToOneRelation,
|
||||||
OneToOneRelation,
|
OneToOneRelation,
|
||||||
PolymorphicRelation,
|
PolymorphicRelation,
|
||||||
RelationField,
|
RelationField,
|
||||||
} from "../../src/data/relations";
|
} from "data/relations";
|
||||||
import { getDummyConnection } from "./helper";
|
import { getDummyConnection } from "./helper";
|
||||||
|
|
||||||
const { dummyConnection, afterAllCleanup } = getDummyConnection();
|
const { dummyConnection, afterAllCleanup } = getDummyConnection();
|
||||||
@@ -77,7 +78,7 @@ describe("Relations", async () => {
|
|||||||
const em = new EntityManager(entities, dummyConnection, relations);
|
const em = new EntityManager(entities, dummyConnection, relations);
|
||||||
|
|
||||||
// verify naming
|
// 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.entity.name).toBe(posts.name);
|
||||||
expect(rel.source.reference).toBe(posts.name);
|
expect(rel.source.reference).toBe(posts.name);
|
||||||
expect(rel.target.entity.name).toBe(users.name);
|
expect(rel.target.entity.name).toBe(users.name);
|
||||||
@@ -89,11 +90,11 @@ describe("Relations", async () => {
|
|||||||
// verify low level relation
|
// 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).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(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).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)
|
// verify high level relation (from users)
|
||||||
const userPostsRel = em.relationOf(users.name, "posts");
|
const userPostsRel = em.relationOf(users.name, "posts");
|
||||||
@@ -191,7 +192,7 @@ describe("Relations", async () => {
|
|||||||
const em = new EntityManager(entities, dummyConnection, relations);
|
const em = new EntityManager(entities, dummyConnection, relations);
|
||||||
|
|
||||||
// verify naming
|
// 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.entity.name).toBe(users.name);
|
||||||
expect(rel.source.reference).toBe(users.name);
|
expect(rel.source.reference).toBe(users.name);
|
||||||
expect(rel.target.entity.name).toBe(settings.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).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]!.source.entity).toBe(users);
|
||||||
expect(em.relationsOf(users.name)[0].target.entity).toBe(settings);
|
expect(em.relationsOf(users.name)[0]!.target.entity).toBe(settings);
|
||||||
|
|
||||||
// verify high level relation (from users)
|
// verify high level relation (from users)
|
||||||
const userSettingRel = em.relationOf(users.name, settings.name);
|
const userSettingRel = em.relationOf(users.name, settings.name);
|
||||||
@@ -323,7 +324,7 @@ describe("Relations", async () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// mutation info
|
// mutation info
|
||||||
expect(relations[0].helper(posts.name)!.getMutationInfo()).toEqual({
|
expect(relations[0]!.helper(posts.name)!.getMutationInfo()).toEqual({
|
||||||
reference: "categories",
|
reference: "categories",
|
||||||
local_field: undefined,
|
local_field: undefined,
|
||||||
$set: false,
|
$set: false,
|
||||||
@@ -334,7 +335,7 @@ describe("Relations", async () => {
|
|||||||
cardinality: undefined,
|
cardinality: undefined,
|
||||||
relation_type: "m:n",
|
relation_type: "m:n",
|
||||||
});
|
});
|
||||||
expect(relations[0].helper(categories.name)!.getMutationInfo()).toEqual({
|
expect(relations[0]!.helper(categories.name)!.getMutationInfo()).toEqual({
|
||||||
reference: "posts",
|
reference: "posts",
|
||||||
local_field: undefined,
|
local_field: undefined,
|
||||||
$set: false,
|
$set: false,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { describe, expect, test } from "bun:test";
|
import { describe, expect, test } from "bun:test";
|
||||||
import { Entity, NumberField, TextField } from "data";
|
import { Entity } from "data/entities";
|
||||||
import * as p from "data/prototype";
|
import { NumberField, TextField } from "data/fields";
|
||||||
|
|
||||||
describe("[data] Entity", async () => {
|
describe("[data] Entity", async () => {
|
||||||
const entity = new Entity("test", [
|
const entity = new Entity("test", [
|
||||||
|
|||||||
@@ -1,12 +1,8 @@
|
|||||||
import { afterAll, describe, expect, test } from "bun:test";
|
import { afterAll, describe, expect, test } from "bun:test";
|
||||||
import {
|
import { Entity, EntityManager } from "data/entities";
|
||||||
Entity,
|
import { ManyToManyRelation, ManyToOneRelation } from "data/relations";
|
||||||
EntityManager,
|
import { SchemaManager } from "data/schema/SchemaManager";
|
||||||
ManyToManyRelation,
|
import { UnableToConnectException } from "data/errors";
|
||||||
ManyToOneRelation,
|
|
||||||
SchemaManager,
|
|
||||||
} from "../../../src/data";
|
|
||||||
import { UnableToConnectException } from "../../../src/data/errors";
|
|
||||||
import { getDummyConnection } from "../helper";
|
import { getDummyConnection } from "../helper";
|
||||||
|
|
||||||
const { dummyConnection, afterAllCleanup } = getDummyConnection();
|
const { dummyConnection, afterAllCleanup } = getDummyConnection();
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import { afterAll, describe, expect, test } from "bun:test";
|
import { afterAll, describe, expect, test } from "bun:test";
|
||||||
import { Entity, EntityManager, ManyToOneRelation, TextField } from "../../../src/data";
|
import { Entity, EntityManager } from "data/entities";
|
||||||
import { JoinBuilder } from "../../../src/data/entities/query/JoinBuilder";
|
import { ManyToOneRelation } from "data/relations";
|
||||||
|
import { TextField } from "data/fields";
|
||||||
|
import { JoinBuilder } from "data/entities/query/JoinBuilder";
|
||||||
import { getDummyConnection } from "../helper";
|
import { getDummyConnection } from "../helper";
|
||||||
|
|
||||||
const { dummyConnection, afterAllCleanup } = getDummyConnection();
|
const { dummyConnection, afterAllCleanup } = getDummyConnection();
|
||||||
|
|||||||
@@ -1,18 +1,16 @@
|
|||||||
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
||||||
import type { EventManager } from "../../../src/core/events";
|
import type { EventManager } from "../../../src/core/events";
|
||||||
|
import { Entity, EntityManager } from "data/entities";
|
||||||
import {
|
import {
|
||||||
Entity,
|
|
||||||
EntityManager,
|
|
||||||
ManyToOneRelation,
|
ManyToOneRelation,
|
||||||
MutatorEvents,
|
|
||||||
NumberField,
|
|
||||||
OneToOneRelation,
|
OneToOneRelation,
|
||||||
type RelationField,
|
RelationField,
|
||||||
RelationMutator,
|
RelationMutator,
|
||||||
TextField,
|
} from "data/relations";
|
||||||
} from "../../../src/data";
|
import { NumberField, TextField } from "data/fields";
|
||||||
import * as proto from "../../../src/data/prototype";
|
import * as proto from "data/prototype";
|
||||||
import { getDummyConnection, disableConsoleLog, enableConsoleLog } from "../../helper";
|
import { getDummyConnection, disableConsoleLog, enableConsoleLog } from "../../helper";
|
||||||
|
import { MutatorEvents } from "data/events";
|
||||||
|
|
||||||
const { dummyConnection, afterAllCleanup } = getDummyConnection();
|
const { dummyConnection, afterAllCleanup } = getDummyConnection();
|
||||||
afterAll(afterAllCleanup);
|
afterAll(afterAllCleanup);
|
||||||
|
|||||||
@@ -1,17 +1,10 @@
|
|||||||
import { afterAll, describe, expect, test } from "bun:test";
|
import { afterAll, describe, expect, test } from "bun:test";
|
||||||
import type { Kysely, Transaction } from "kysely";
|
import type { Kysely, Transaction } from "kysely";
|
||||||
import { Perf } from "core/utils";
|
import { TextField } from "data/fields";
|
||||||
import {
|
import { em as $em, entity as $entity, text as $text } from "data/prototype";
|
||||||
Entity,
|
import { Entity, EntityManager } from "data/entities";
|
||||||
EntityManager,
|
import { ManyToOneRelation } from "data/relations";
|
||||||
LibsqlConnection,
|
import { RepositoryEvents } from "data/events";
|
||||||
ManyToOneRelation,
|
|
||||||
RepositoryEvents,
|
|
||||||
TextField,
|
|
||||||
entity as $entity,
|
|
||||||
text as $text,
|
|
||||||
em as $em,
|
|
||||||
} from "data";
|
|
||||||
import { getDummyConnection } from "../helper";
|
import { getDummyConnection } from "../helper";
|
||||||
|
|
||||||
type E = Kysely<any> | Transaction<any>;
|
type E = Kysely<any> | Transaction<any>;
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
// eslint-disable-next-line import/no-unresolved
|
// eslint-disable-next-line import/no-unresolved
|
||||||
import { afterAll, describe, expect, test } from "bun:test";
|
import { afterAll, describe, expect, test } from "bun:test";
|
||||||
import { randomString } from "../../../src/core/utils";
|
import { randomString } from "core/utils";
|
||||||
import { Entity, EntityIndex, EntityManager, SchemaManager, TextField } from "../../../src/data";
|
import { Entity, EntityManager } from "data/entities";
|
||||||
|
import { TextField, EntityIndex } from "data/fields";
|
||||||
|
import { SchemaManager } from "data/schema/SchemaManager";
|
||||||
import { getDummyConnection } from "../helper";
|
import { getDummyConnection } from "../helper";
|
||||||
|
|
||||||
const { dummyConnection, afterAllCleanup } = getDummyConnection();
|
const { dummyConnection, afterAllCleanup } = getDummyConnection();
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { describe, test, expect } from "bun:test";
|
import { describe, test, expect } from "bun:test";
|
||||||
import { getDummyConnection } from "../helper";
|
import { getDummyConnection } from "../helper";
|
||||||
import { type WhereQuery, WhereBuilder } from "data";
|
import { WhereBuilder, type WhereQuery } from "data/entities/query/WhereBuilder";
|
||||||
|
|
||||||
function qb() {
|
function qb() {
|
||||||
const c = getDummyConnection();
|
const c = getDummyConnection();
|
||||||
|
|||||||
@@ -1,14 +1,9 @@
|
|||||||
import { describe, expect, test } from "bun:test";
|
import { describe, expect, test } from "bun:test";
|
||||||
import {
|
import { Entity, EntityManager } from "data/entities";
|
||||||
Entity,
|
import { ManyToManyRelation, ManyToOneRelation, PolymorphicRelation } from "data/relations";
|
||||||
EntityManager,
|
import { TextField } from "data/fields";
|
||||||
ManyToManyRelation,
|
import * as proto from "data/prototype";
|
||||||
ManyToOneRelation,
|
import { WithBuilder } from "data/entities/query/WithBuilder";
|
||||||
PolymorphicRelation,
|
|
||||||
TextField,
|
|
||||||
WithBuilder,
|
|
||||||
} from "../../../src/data";
|
|
||||||
import * as proto from "../../../src/data/prototype";
|
|
||||||
import { schemaToEm } from "../../helper";
|
import { schemaToEm } from "../../helper";
|
||||||
import { getDummyConnection } from "../helper";
|
import { getDummyConnection } from "../helper";
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { afterAll, describe, expect, test } from "bun:test";
|
import { afterAll, describe, expect, test } from "bun:test";
|
||||||
import { EntityManager } from "../../../../src/data";
|
import { EntityManager } from "data/entities/EntityManager";
|
||||||
import { getDummyConnection } from "../../helper";
|
import { getDummyConnection } from "../../helper";
|
||||||
|
|
||||||
const { dummyConnection, afterAllCleanup } = getDummyConnection();
|
const { dummyConnection, afterAllCleanup } = getDummyConnection();
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
|
import { bunTestRunner } from "adapter/bun/test";
|
||||||
import { describe, expect, test } from "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";
|
import { fieldTestSuite, transformPersist } from "data/fields/field-test-suite";
|
||||||
|
|
||||||
describe("[data] BooleanField", async () => {
|
describe("[data] BooleanField", async () => {
|
||||||
fieldTestSuite({ expect, test }, BooleanField, { defaultValue: true, schemaType: "boolean" });
|
fieldTestSuite(bunTestRunner, BooleanField, { defaultValue: true, schemaType: "boolean" });
|
||||||
|
|
||||||
test("transformRetrieve", async () => {
|
test("transformRetrieve", async () => {
|
||||||
const field = new BooleanField("test");
|
const field = new BooleanField("test");
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { describe, expect, test } from "bun:test";
|
import { describe, test } from "bun:test";
|
||||||
import { DateField, dateFieldConfigSchema } from "../../../../src/data";
|
import { DateField } from "data/fields";
|
||||||
import { fieldTestSuite } from "data/fields/field-test-suite";
|
import { fieldTestSuite } from "data/fields/field-test-suite";
|
||||||
import { bunTestRunner } from "adapter/bun/test";
|
import { bunTestRunner } from "adapter/bun/test";
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
|
import { bunTestRunner } from "adapter/bun/test";
|
||||||
import { describe, expect, test } from "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";
|
import { fieldTestSuite, transformPersist } from "data/fields/field-test-suite";
|
||||||
|
|
||||||
function options(strings: string[]) {
|
function options(strings: string[]) {
|
||||||
@@ -8,7 +9,7 @@ function options(strings: string[]) {
|
|||||||
|
|
||||||
describe("[data] EnumField", async () => {
|
describe("[data] EnumField", async () => {
|
||||||
fieldTestSuite(
|
fieldTestSuite(
|
||||||
{ expect, test },
|
bunTestRunner,
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
EnumField,
|
EnumField,
|
||||||
{ defaultValue: "a", schemaType: "text" },
|
{ defaultValue: "a", schemaType: "text" },
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { describe, expect, test } from "bun:test";
|
|||||||
import { baseFieldConfigSchema, Field } from "../../../../src/data/fields/Field";
|
import { baseFieldConfigSchema, Field } from "../../../../src/data/fields/Field";
|
||||||
import { fieldTestSuite } from "data/fields/field-test-suite";
|
import { fieldTestSuite } from "data/fields/field-test-suite";
|
||||||
import { bunTestRunner } from "adapter/bun/test";
|
import { bunTestRunner } from "adapter/bun/test";
|
||||||
import { stripMark } from "core/object/schema";
|
import { stripMark } from "core/utils/schema";
|
||||||
|
|
||||||
describe("[data] Field", async () => {
|
describe("[data] Field", async () => {
|
||||||
class FieldSpec extends Field {
|
class FieldSpec extends Field {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { describe, expect, test } from "bun:test";
|
import { describe, expect, test } from "bun:test";
|
||||||
import { Entity, EntityIndex, Field } from "../../../../src/data";
|
import { Entity } from "data/entities";
|
||||||
import { s } from "core/object/schema";
|
import { Field, EntityIndex } from "data/fields";
|
||||||
|
import { s } from "core/utils/schema";
|
||||||
|
|
||||||
class TestField extends Field {
|
class TestField extends Field {
|
||||||
protected getSchema(): any {
|
protected getSchema(): any {
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
|
import { bunTestRunner } from "adapter/bun/test";
|
||||||
import { describe, expect, test } from "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";
|
import { fieldTestSuite, transformPersist } from "data/fields/field-test-suite";
|
||||||
|
|
||||||
describe("[data] JsonField", async () => {
|
describe("[data] JsonField", async () => {
|
||||||
const field = new JsonField("test");
|
const field = new JsonField("test");
|
||||||
fieldTestSuite({ expect, test }, JsonField, {
|
fieldTestSuite(bunTestRunner, JsonField, {
|
||||||
defaultValue: { a: 1 },
|
defaultValue: { a: 1 },
|
||||||
sampleValues: ["string", { test: 1 }, 1],
|
sampleValues: ["string", { test: 1 }, 1],
|
||||||
schemaType: "text",
|
schemaType: "text",
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { describe, expect, test } from "bun:test";
|
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";
|
import { fieldTestSuite } from "data/fields/field-test-suite";
|
||||||
|
|
||||||
describe("[data] JsonSchemaField", async () => {
|
describe("[data] JsonSchemaField", async () => {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
|
import { bunTestRunner } from "adapter/bun/test";
|
||||||
import { describe, expect, test } from "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";
|
import { fieldTestSuite, transformPersist } from "data/fields/field-test-suite";
|
||||||
|
|
||||||
describe("[data] NumberField", async () => {
|
describe("[data] NumberField", async () => {
|
||||||
@@ -15,5 +16,5 @@ describe("[data] NumberField", async () => {
|
|||||||
expect(transformPersist(field2, 10000)).resolves.toBe(10000);
|
expect(transformPersist(field2, 10000)).resolves.toBe(10000);
|
||||||
});
|
});
|
||||||
|
|
||||||
fieldTestSuite({ expect, test }, NumberField, { defaultValue: 12, schemaType: "integer" });
|
fieldTestSuite(bunTestRunner, NumberField, { defaultValue: 12, schemaType: "integer" });
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { describe, expect, test } from "bun:test";
|
import { describe, expect, test } from "bun:test";
|
||||||
import { PrimaryField } from "../../../../src/data";
|
import { PrimaryField } from "data/fields";
|
||||||
|
|
||||||
describe("[data] PrimaryField", async () => {
|
describe("[data] PrimaryField", async () => {
|
||||||
const field = new PrimaryField("primary");
|
const field = new PrimaryField("primary");
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { describe, expect, test } from "bun:test";
|
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 { fieldTestSuite, transformPersist } from "data/fields/field-test-suite";
|
||||||
import { bunTestRunner } from "adapter/bun/test";
|
import { bunTestRunner } from "adapter/bun/test";
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import { describe, expect, it, test } from "bun:test";
|
import { describe, expect, it, test } from "bun:test";
|
||||||
import { Entity, type EntityManager } from "../../../../src/data";
|
import { Entity, type EntityManager } from "data/entities";
|
||||||
import {
|
import {
|
||||||
type BaseRelationConfig,
|
type BaseRelationConfig,
|
||||||
EntityRelation,
|
EntityRelation,
|
||||||
EntityRelationAnchor,
|
EntityRelationAnchor,
|
||||||
RelationTypes,
|
RelationTypes,
|
||||||
} from "../../../../src/data/relations";
|
} from "data/relations";
|
||||||
|
|
||||||
class TestEntityRelation extends EntityRelation {
|
class TestEntityRelation extends EntityRelation {
|
||||||
constructor(config?: BaseRelationConfig) {
|
constructor(config?: BaseRelationConfig) {
|
||||||
@@ -24,11 +24,11 @@ class TestEntityRelation extends EntityRelation {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
buildWith(a: any, b: any, c: any): any {
|
buildWith(): any {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
buildJoin(a: any, b: any): any {
|
buildJoin(): any {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { describe, expect, test } from "bun:test";
|
import { describe, expect, test } from "bun:test";
|
||||||
import { Flow, LogTask, SubFlowTask, RenderTask, Task } from "../../src/flows";
|
import { Flow, LogTask, SubFlowTask, RenderTask, Task } from "../../src/flows";
|
||||||
import { s } from "core/object/schema";
|
import { s } from "core/utils/schema";
|
||||||
|
|
||||||
export class StringifyTask<Output extends string> extends Task<
|
export class StringifyTask<Output extends string> extends Task<
|
||||||
typeof StringifyTask.schema,
|
typeof StringifyTask.schema,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { describe, expect, test } from "bun:test";
|
import { describe, expect, test } from "bun:test";
|
||||||
import { Task } from "../../src/flows";
|
import { Task } from "../../src/flows";
|
||||||
import { dynamic } from "../../src/flows/tasks/Task";
|
import { dynamic } from "../../src/flows/tasks/Task";
|
||||||
import { s } from "core/object/schema";
|
import { s } from "core/utils/schema";
|
||||||
|
|
||||||
describe.skip("Task", async () => {
|
describe.skip("Task", async () => {
|
||||||
test("resolveParams: template with parse", async () => {
|
test("resolveParams: template with parse", async () => {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { describe, expect, test } from "bun:test";
|
import { describe, expect, test } from "bun:test";
|
||||||
import { Hono } from "hono";
|
import { Hono } from "hono";
|
||||||
import { Event, EventManager } from "../../src/core/events";
|
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 { EventTrigger, Flow, HttpTrigger, type InputsMap, Task } from "../../src/flows";
|
||||||
import { dynamic } from "../../src/flows/tasks/Task";
|
import { dynamic } from "../../src/flows/tasks/Task";
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import { describe, expect, test } from "bun:test";
|
import { describe, expect, test } from "bun:test";
|
||||||
import { isEqual } from "lodash-es";
|
import { isEqual } from "lodash-es";
|
||||||
import { _jsonp, withDisabledConsole } from "../../src/core/utils";
|
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";
|
import { Condition, ExecutionEvent, FetchTask, Flow, LogTask, Task } from "../../src/flows";
|
||||||
|
|
||||||
/*beforeAll(disableConsoleLog);
|
/*beforeAll(disableConsoleLog);
|
||||||
|
|||||||
@@ -2,11 +2,12 @@ import { unlink } from "node:fs/promises";
|
|||||||
import type { SelectQueryBuilder, SqliteDatabase } from "kysely";
|
import type { SelectQueryBuilder, SqliteDatabase } from "kysely";
|
||||||
import Database from "libsql";
|
import Database from "libsql";
|
||||||
import { format as sqlFormat } from "sql-formatter";
|
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 type { em as protoEm } from "../src/data/prototype";
|
||||||
import { writeFile } from "node:fs/promises";
|
import { writeFile } from "node:fs/promises";
|
||||||
import { join } from "node:path";
|
import { join } from "node:path";
|
||||||
import { slugify } from "core/utils/strings";
|
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): {
|
export function getDummyDatabase(memory: boolean = true): {
|
||||||
dummyDb: SqliteDatabase;
|
dummyDb: SqliteDatabase;
|
||||||
|
|||||||
@@ -54,7 +54,6 @@ describe("MediaController", () => {
|
|||||||
body: file,
|
body: file,
|
||||||
});
|
});
|
||||||
const result = (await res.json()) as any;
|
const result = (await res.json()) as any;
|
||||||
console.log(result);
|
|
||||||
expect(result.name).toBe(name);
|
expect(result.name).toBe(name);
|
||||||
|
|
||||||
const destFile = Bun.file(assetsTmpPath + "/" + name);
|
const destFile = Bun.file(assetsTmpPath + "/" + name);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { describe, expect, test } from "bun:test";
|
import { describe, expect, test } from "bun:test";
|
||||||
import { type FileBody, Storage } from "../../src/media/storage/Storage";
|
import { type FileBody, Storage } from "../../src/media/storage/Storage";
|
||||||
import * as StorageEvents from "../../src/media/storage/events";
|
import * as StorageEvents from "../../src/media/storage/events";
|
||||||
import { StorageAdapter } from "media";
|
import { StorageAdapter } from "media/storage/StorageAdapter";
|
||||||
|
|
||||||
class TestAdapter extends StorageAdapter {
|
class TestAdapter extends StorageAdapter {
|
||||||
files: Record<string, FileBody> = {};
|
files: Record<string, FileBody> = {};
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import { afterAll, beforeAll, beforeEach, describe, expect, spyOn, test } from "bun:test";
|
import { afterAll, beforeAll, beforeEach, describe, expect, spyOn, test } from "bun:test";
|
||||||
import { createApp } from "core/test/utils";
|
import { createApp } from "core/test/utils";
|
||||||
import { AuthController } from "../../src/auth/api/AuthController";
|
import { AuthController } from "../../src/auth/api/AuthController";
|
||||||
import { em, entity, make, text } from "../../src/data";
|
import { em, entity, make, text } from "data/prototype";
|
||||||
import { AppAuth, type ModuleBuildContext } from "../../src/modules";
|
import { AppAuth, type ModuleBuildContext } from "modules";
|
||||||
import { disableConsoleLog, enableConsoleLog } from "../helper";
|
import { disableConsoleLog, enableConsoleLog } from "../helper";
|
||||||
// @ts-ignore
|
|
||||||
import { makeCtx, moduleTestSuite } from "./module-test-suite";
|
import { makeCtx, moduleTestSuite } from "./module-test-suite";
|
||||||
|
|
||||||
describe("AppAuth", () => {
|
describe("AppAuth", () => {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { beforeEach, describe, expect, test } from "bun:test";
|
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 { fieldsSchema } from "../../src/data/data-schema";
|
||||||
import { AppData, type ModuleBuildContext } from "../../src/modules";
|
import { AppData, type ModuleBuildContext } from "../../src/modules";
|
||||||
import { makeCtx, moduleTestSuite } from "./module-test-suite";
|
import { makeCtx, moduleTestSuite } from "./module-test-suite";
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import { describe, expect, test } from "bun:test";
|
import { describe, expect, test } from "bun:test";
|
||||||
import { registries } from "../../src";
|
|
||||||
import { createApp } from "core/test/utils";
|
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 { StorageLocalAdapter } from "adapter/node/storage/StorageLocalAdapter";
|
||||||
import { AppMedia } from "../../src/media/AppMedia";
|
import { AppMedia } from "../../src/media/AppMedia";
|
||||||
import { mediaConfigSchema } from "../../src/media/media-schema";
|
|
||||||
import { moduleTestSuite } from "./module-test-suite";
|
import { moduleTestSuite } from "./module-test-suite";
|
||||||
|
|
||||||
describe("AppMedia", () => {
|
describe("AppMedia", () => {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { describe, expect, test } from "bun:test";
|
import { describe, expect, test } from "bun:test";
|
||||||
import { s, stripMark } from "core/object/schema";
|
import { s, stripMark } from "core/utils/schema";
|
||||||
import { EntityManager, em, entity, index, text } from "../../src/data";
|
import { em, entity, index, text } from "data/prototype";
|
||||||
|
import { EntityManager } from "data/entities/EntityManager";
|
||||||
import { DummyConnection } from "../../src/data/connection/DummyConnection";
|
import { DummyConnection } from "../../src/data/connection/DummyConnection";
|
||||||
import { Module } from "../../src/modules/Module";
|
import { Module } from "../../src/modules/Module";
|
||||||
import { ModuleHelper } from "modules/ModuleHelper";
|
import { ModuleHelper } from "modules/ModuleHelper";
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
|
import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
|
||||||
import { disableConsoleLog, enableConsoleLog } from "core/utils";
|
import { disableConsoleLog, enableConsoleLog } from "core/utils";
|
||||||
import { Connection, entity, text } from "data";
|
|
||||||
import { Module } from "modules/Module";
|
import { Module } from "modules/Module";
|
||||||
import { type ConfigTable, getDefaultConfig, ModuleManager } from "modules/ModuleManager";
|
import { type ConfigTable, getDefaultConfig, ModuleManager } from "modules/ModuleManager";
|
||||||
import { CURRENT_VERSION, TABLE_NAME } from "modules/migrations";
|
import { CURRENT_VERSION, TABLE_NAME } from "modules/migrations";
|
||||||
import { getDummyConnection } from "../helper";
|
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 () => {
|
describe("ModuleManager", async () => {
|
||||||
test("s1: no config, no build", async () => {
|
test("s1: no config, no build", async () => {
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import { beforeEach, describe, expect, it } from "bun:test";
|
import { beforeEach, describe, expect, it } from "bun:test";
|
||||||
|
|
||||||
import { Hono } from "hono";
|
import { Hono } from "hono";
|
||||||
import { Guard } from "../../src/auth";
|
import { Guard } from "auth/authorize/Guard";
|
||||||
import { DebugLogger } from "../../src/core";
|
import { DebugLogger } from "core/utils/DebugLogger";
|
||||||
import { EventManager } from "../../src/core/events";
|
import { EventManager } from "core/events";
|
||||||
import { EntityManager } from "../../src/data";
|
import { EntityManager } from "data/entities/EntityManager";
|
||||||
import { Module, type ModuleBuildContext } from "../../src/modules/Module";
|
import { Module, type ModuleBuildContext } from "modules/Module";
|
||||||
import { getDummyConnection } from "../helper";
|
import { getDummyConnection } from "../helper";
|
||||||
import { ModuleHelper } from "modules/ModuleHelper";
|
import { ModuleHelper } from "modules/ModuleHelper";
|
||||||
|
|
||||||
|
|||||||
+1
-8
@@ -79,14 +79,7 @@ async function buildApi() {
|
|||||||
sourcemap,
|
sourcemap,
|
||||||
watch,
|
watch,
|
||||||
define,
|
define,
|
||||||
entry: [
|
entry: ["src/index.ts", "src/core/utils/index.ts", "src/plugins/index.ts"],
|
||||||
"src/index.ts",
|
|
||||||
"src/core/index.ts",
|
|
||||||
"src/core/utils/index.ts",
|
|
||||||
"src/data/index.ts",
|
|
||||||
"src/media/index.ts",
|
|
||||||
"src/plugins/index.ts",
|
|
||||||
],
|
|
||||||
outDir: "dist",
|
outDir: "dist",
|
||||||
external: [...external],
|
external: [...external],
|
||||||
metafile: true,
|
metafile: true,
|
||||||
|
|||||||
+5
-22
@@ -13,6 +13,7 @@
|
|||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/bknd-io/bknd/issues"
|
"url": "https://github.com/bknd-io/bknd/issues"
|
||||||
},
|
},
|
||||||
|
"packageManager": "bun@1.2.19",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=22"
|
"node": ">=22"
|
||||||
},
|
},
|
||||||
@@ -60,8 +61,7 @@
|
|||||||
"bcryptjs": "^3.0.2",
|
"bcryptjs": "^3.0.2",
|
||||||
"dayjs": "^1.11.13",
|
"dayjs": "^1.11.13",
|
||||||
"fast-xml-parser": "^5.0.8",
|
"fast-xml-parser": "^5.0.8",
|
||||||
"hono": "^4.7.11",
|
"hono": "4.8.3",
|
||||||
"json-schema-form-react": "^0.0.2",
|
|
||||||
"json-schema-library": "10.0.0-rc7",
|
"json-schema-library": "10.0.0-rc7",
|
||||||
"json-schema-to-ts": "^3.1.1",
|
"json-schema-to-ts": "^3.1.1",
|
||||||
"kysely": "^0.27.6",
|
"kysely": "^0.27.6",
|
||||||
@@ -100,7 +100,7 @@
|
|||||||
"dotenv": "^16.4.7",
|
"dotenv": "^16.4.7",
|
||||||
"jotai": "^2.12.2",
|
"jotai": "^2.12.2",
|
||||||
"jsdom": "^26.0.0",
|
"jsdom": "^26.0.0",
|
||||||
"jsonv-ts": "^0.2.2",
|
"jsonv-ts": "^0.3.2",
|
||||||
"kysely-d1": "^0.3.0",
|
"kysely-d1": "^0.3.0",
|
||||||
"kysely-generic-sqlite": "^1.2.1",
|
"kysely-generic-sqlite": "^1.2.1",
|
||||||
"libsql-stateless-easy": "^1.8.0",
|
"libsql-stateless-easy": "^1.8.0",
|
||||||
@@ -161,16 +161,6 @@
|
|||||||
"import": "./dist/ui/client/index.js",
|
"import": "./dist/ui/client/index.js",
|
||||||
"require": "./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": {
|
"./utils": {
|
||||||
"types": "./dist/types/core/utils/index.d.ts",
|
"types": "./dist/types/core/utils/index.d.ts",
|
||||||
"import": "./dist/core/utils/index.js",
|
"import": "./dist/core/utils/index.js",
|
||||||
@@ -181,11 +171,6 @@
|
|||||||
"import": "./dist/cli/index.js",
|
"import": "./dist/cli/index.js",
|
||||||
"require": "./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": {
|
"./plugins": {
|
||||||
"types": "./dist/types/plugins/index.d.ts",
|
"types": "./dist/types/plugins/index.d.ts",
|
||||||
"import": "./dist/plugins/index.js",
|
"import": "./dist/plugins/index.js",
|
||||||
@@ -251,15 +236,13 @@
|
|||||||
},
|
},
|
||||||
"./dist/main.css": "./dist/ui/main.css",
|
"./dist/main.css": "./dist/ui/main.css",
|
||||||
"./dist/styles.css": "./dist/ui/styles.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": {
|
"typesVersions": {
|
||||||
"*": {
|
"*": {
|
||||||
"data": ["./dist/types/data/index.d.ts"],
|
|
||||||
"core": ["./dist/types/core/index.d.ts"],
|
|
||||||
"utils": ["./dist/types/core/utils/index.d.ts"],
|
"utils": ["./dist/types/core/utils/index.d.ts"],
|
||||||
"cli": ["./dist/types/cli/index.d.ts"],
|
"cli": ["./dist/types/cli/index.d.ts"],
|
||||||
"media": ["./dist/types/media/index.d.ts"],
|
|
||||||
"plugins": ["./dist/types/plugins/index.d.ts"],
|
"plugins": ["./dist/types/plugins/index.d.ts"],
|
||||||
"adapter": ["./dist/types/adapter/index.d.ts"],
|
"adapter": ["./dist/types/adapter/index.d.ts"],
|
||||||
"adapter/cloudflare": ["./dist/types/adapter/cloudflare/index.d.ts"],
|
"adapter/cloudflare": ["./dist/types/adapter/cloudflare/index.d.ts"],
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
import type { SafeUser } from "auth";
|
import type { SafeUser } from "bknd";
|
||||||
import { AuthApi, type AuthApiOptions } from "auth/api/AuthApi";
|
import { AuthApi, type AuthApiOptions } from "auth/api/AuthApi";
|
||||||
import { DataApi, type DataApiOptions } from "data/api/DataApi";
|
import { DataApi, type DataApiOptions } from "data/api/DataApi";
|
||||||
import { decode } from "hono/jwt";
|
import { decode } from "hono/jwt";
|
||||||
|
|||||||
@@ -3,10 +3,9 @@
|
|||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { type RuntimeBkndConfig, createRuntimeApp, type RuntimeOptions } from "bknd/adapter";
|
import { type RuntimeBkndConfig, createRuntimeApp, type RuntimeOptions } from "bknd/adapter";
|
||||||
import { registerLocalMediaAdapter } from ".";
|
import { registerLocalMediaAdapter } from ".";
|
||||||
import { config } from "bknd/core";
|
import { config, type App } from "bknd";
|
||||||
import type { ServeOptions } from "bun";
|
import type { ServeOptions } from "bun";
|
||||||
import { serveStatic } from "hono/bun";
|
import { serveStatic } from "hono/bun";
|
||||||
import type { App } from "App";
|
|
||||||
|
|
||||||
type BunEnv = Bun.Env;
|
type BunEnv = Bun.Env;
|
||||||
export type BunBkndConfig<Env = BunEnv> = RuntimeBkndConfig<Env> & Omit<ServeOptions, "fetch">;
|
export type BunBkndConfig<Env = BunEnv> = RuntimeBkndConfig<Env> & Omit<ServeOptions, "fetch">;
|
||||||
@@ -21,8 +20,8 @@ export async function createApp<Env = BunEnv>(
|
|||||||
|
|
||||||
return await createRuntimeApp(
|
return await createRuntimeApp(
|
||||||
{
|
{
|
||||||
...config,
|
|
||||||
serveStatic: serveStatic({ root }),
|
serveStatic: serveStatic({ root }),
|
||||||
|
...config,
|
||||||
},
|
},
|
||||||
args ?? (process.env as Env),
|
args ?? (process.env as Env),
|
||||||
opts,
|
opts,
|
||||||
@@ -53,6 +52,7 @@ export function serve<Env = BunEnv>(
|
|||||||
onBuilt,
|
onBuilt,
|
||||||
buildConfig,
|
buildConfig,
|
||||||
adminOptions,
|
adminOptions,
|
||||||
|
serveStatic,
|
||||||
...serveOptions
|
...serveOptions
|
||||||
}: BunBkndConfig<Env> = {},
|
}: BunBkndConfig<Env> = {},
|
||||||
args: Env = {} as Env,
|
args: Env = {} as Env,
|
||||||
@@ -70,6 +70,7 @@ export function serve<Env = BunEnv>(
|
|||||||
buildConfig,
|
buildConfig,
|
||||||
adminOptions,
|
adminOptions,
|
||||||
distPath,
|
distPath,
|
||||||
|
serveStatic,
|
||||||
},
|
},
|
||||||
args,
|
args,
|
||||||
opts,
|
opts,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Database } from "bun:sqlite";
|
import { Database } from "bun:sqlite";
|
||||||
import { genericSqlite, type GenericSqliteConnection } from "bknd/data";
|
import { genericSqlite, type GenericSqliteConnection } from "bknd";
|
||||||
|
|
||||||
export type BunSqliteConnection = GenericSqliteConnection<Database>;
|
export type BunSqliteConnection = GenericSqliteConnection<Database>;
|
||||||
export type BunSqliteConnectionConfig = {
|
export type BunSqliteConnectionConfig = {
|
||||||
|
|||||||
@@ -12,7 +12,10 @@ export function getBindings<T extends GetBindingType>(env: any, type: T): Bindin
|
|||||||
const bindings: BindingMap<T>[] = [];
|
const bindings: BindingMap<T>[] = [];
|
||||||
for (const key in env) {
|
for (const key in env) {
|
||||||
try {
|
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({
|
bindings.push({
|
||||||
key,
|
key,
|
||||||
value: env[key] as BindingTypeMap[T],
|
value: env[key] as BindingTypeMap[T],
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
/// <reference types="@cloudflare/workers-types" />
|
/// <reference types="@cloudflare/workers-types" />
|
||||||
|
|
||||||
|
import { Connection } from "bknd";
|
||||||
|
import { sqlite } from "bknd/adapter/sqlite";
|
||||||
|
import { makeConfig as makeAdapterConfig } from "bknd/adapter";
|
||||||
import { registerMedia } from "./storage/StorageR2Adapter";
|
import { registerMedia } from "./storage/StorageR2Adapter";
|
||||||
import { getBinding } from "./bindings";
|
import { getBinding } from "./bindings";
|
||||||
import { d1Sqlite } from "./connection/D1Connection";
|
import { d1Sqlite } from "./connection/D1Connection";
|
||||||
import { Connection } from "bknd/data";
|
|
||||||
import type { CloudflareBkndConfig, CloudflareEnv } from ".";
|
import type { CloudflareBkndConfig, CloudflareEnv } from ".";
|
||||||
import { App } from "bknd";
|
import { App } from "bknd";
|
||||||
import { makeConfig as makeAdapterConfig } from "bknd/adapter";
|
|
||||||
import type { Context, ExecutionContext } from "hono";
|
import type { Context, ExecutionContext } from "hono";
|
||||||
import { $console } from "core/utils";
|
import { $console } from "core/utils";
|
||||||
import { setCookie } from "hono/cookie";
|
import { setCookie } from "hono/cookie";
|
||||||
import { sqlite } from "bknd/adapter/sqlite";
|
|
||||||
|
|
||||||
export const constants = {
|
export const constants = {
|
||||||
exec_async_event_id: "cf_register_waituntil",
|
exec_async_event_id: "cf_register_waituntil",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/// <reference types="@cloudflare/workers-types" />
|
/// <reference types="@cloudflare/workers-types" />
|
||||||
|
|
||||||
import { genericSqlite, type GenericSqliteConnection } from "bknd/data";
|
import { genericSqlite, type GenericSqliteConnection } from "bknd";
|
||||||
import type { QueryResult } from "kysely";
|
import type { QueryResult } from "kysely";
|
||||||
|
|
||||||
export type D1SqliteConnection = GenericSqliteConnection<D1Database>;
|
export type D1SqliteConnection = GenericSqliteConnection<D1Database>;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/// <reference types="@cloudflare/workers-types" />
|
/// <reference types="@cloudflare/workers-types" />
|
||||||
|
|
||||||
import { genericSqlite, type GenericSqliteConnection } from "bknd/data";
|
import { genericSqlite, type GenericSqliteConnection } from "bknd";
|
||||||
import type { QueryResult } from "kysely";
|
import type { QueryResult } from "kysely";
|
||||||
|
|
||||||
export type D1SqliteConnection = GenericSqliteConnection<D1Database>;
|
export type D1SqliteConnection = GenericSqliteConnection<D1Database>;
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export {
|
|||||||
type BindingMap,
|
type BindingMap,
|
||||||
} from "./bindings";
|
} from "./bindings";
|
||||||
export { constants } from "./config";
|
export { constants } from "./config";
|
||||||
export { StorageR2Adapter } from "./storage/StorageR2Adapter";
|
export { StorageR2Adapter, registerMedia } from "./storage/StorageR2Adapter";
|
||||||
export { registries } from "bknd";
|
export { registries } from "bknd";
|
||||||
|
|
||||||
// for compatibility with old code
|
// for compatibility with old code
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import { registries } from "bknd";
|
import { registries, isDebug, guessMimeType } from "bknd";
|
||||||
import { isDebug } from "bknd/core";
|
|
||||||
import { guessMimeType as guess, StorageAdapter, type FileBody } from "bknd/media";
|
|
||||||
import { getBindings } from "../bindings";
|
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[] = []) {
|
export function makeSchema(bindings: string[] = []) {
|
||||||
return s.object(
|
return s.object(
|
||||||
@@ -90,7 +89,7 @@ export class StorageR2Adapter extends StorageAdapter {
|
|||||||
|
|
||||||
const responseHeaders = new Headers({
|
const responseHeaders = new Headers({
|
||||||
"Accept-Ranges": "bytes",
|
"Accept-Ranges": "bytes",
|
||||||
"Content-Type": guess(key),
|
"Content-Type": guessMimeType(key),
|
||||||
});
|
});
|
||||||
|
|
||||||
const range = headers.has("range");
|
const range = headers.has("range");
|
||||||
@@ -142,7 +141,7 @@ export class StorageR2Adapter extends StorageAdapter {
|
|||||||
if (!metadata || Object.keys(metadata).length === 0) {
|
if (!metadata || Object.keys(metadata).length === 0) {
|
||||||
// guessing is especially required for dev environment (miniflare)
|
// guessing is especially required for dev environment (miniflare)
|
||||||
metadata = {
|
metadata = {
|
||||||
contentType: guess(object.key),
|
contentType: guessMimeType(object.key),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,7 +158,7 @@ export class StorageR2Adapter extends StorageAdapter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type: String(head.httpMetadata?.contentType ?? guess(key)),
|
type: String(head.httpMetadata?.contentType ?? guessMimeType(key)),
|
||||||
size: head.size,
|
size: head.size,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,8 @@
|
|||||||
import { App, type CreateAppConfig } from "bknd";
|
import { config as $config, App, type CreateAppConfig, Connection, guessMimeType } from "bknd";
|
||||||
import { config as $config } from "bknd/core";
|
|
||||||
import { $console } from "bknd/utils";
|
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 type { AdminControllerOptions } from "modules/server/AdminController";
|
||||||
import { Connection } from "bknd/data";
|
import type { Manifest } from "vite";
|
||||||
|
|
||||||
export { Connection } from "bknd/data";
|
|
||||||
|
|
||||||
export type BkndConfig<Args = any> = CreateAppConfig & {
|
export type BkndConfig<Args = any> = CreateAppConfig & {
|
||||||
app?: CreateAppConfig | ((args: Args) => CreateAppConfig);
|
app?: CreateAppConfig | ((args: Args) => CreateAppConfig);
|
||||||
@@ -70,9 +67,9 @@ export async function createAdapterApp<Config extends BkndConfig = BkndConfig, A
|
|||||||
connection = config.connection;
|
connection = config.connection;
|
||||||
} else {
|
} else {
|
||||||
const sqlite = (await import("bknd/adapter/sqlite")).sqlite;
|
const sqlite = (await import("bknd/adapter/sqlite")).sqlite;
|
||||||
const conf = config.connection ?? { url: ":memory:" };
|
const conf = appConfig.connection ?? { url: ":memory:" };
|
||||||
connection = sqlite(conf);
|
connection = sqlite(conf);
|
||||||
$console.info(`Using ${connection.name} connection`, conf.url);
|
$console.info(`Using ${connection!.name} connection`, conf.url);
|
||||||
}
|
}
|
||||||
appConfig.connection = connection;
|
appConfig.connection = connection;
|
||||||
}
|
}
|
||||||
@@ -140,3 +137,54 @@ export async function createRuntimeApp<Args = DefaultArgs>(
|
|||||||
|
|
||||||
return app;
|
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();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { genericSqlite } from "bknd/data";
|
import { genericSqlite } from "bknd";
|
||||||
import { DatabaseSync } from "node:sqlite";
|
import { DatabaseSync } from "node:sqlite";
|
||||||
|
|
||||||
export type NodeSqliteConnectionConfig = {
|
export type NodeSqliteConnectionConfig = {
|
||||||
|
|||||||
@@ -3,9 +3,8 @@ import { serve as honoServe } from "@hono/node-server";
|
|||||||
import { serveStatic } from "@hono/node-server/serve-static";
|
import { serveStatic } from "@hono/node-server/serve-static";
|
||||||
import { registerLocalMediaAdapter } from "adapter/node/storage";
|
import { registerLocalMediaAdapter } from "adapter/node/storage";
|
||||||
import { type RuntimeBkndConfig, createRuntimeApp, type RuntimeOptions } from "bknd/adapter";
|
import { type RuntimeBkndConfig, createRuntimeApp, type RuntimeOptions } from "bknd/adapter";
|
||||||
import { config as $config } from "bknd/core";
|
import { config as $config, type App } from "bknd";
|
||||||
import { $console } from "core/utils";
|
import { $console } from "bknd/utils";
|
||||||
import type { App } from "App";
|
|
||||||
|
|
||||||
type NodeEnv = NodeJS.ProcessEnv;
|
type NodeEnv = NodeJS.ProcessEnv;
|
||||||
export type NodeBkndConfig<Env = NodeEnv> = RuntimeBkndConfig<Env> & {
|
export type NodeBkndConfig<Env = NodeEnv> = RuntimeBkndConfig<Env> & {
|
||||||
@@ -32,8 +31,8 @@ export async function createApp<Env = NodeEnv>(
|
|||||||
registerLocalMediaAdapter();
|
registerLocalMediaAdapter();
|
||||||
return await createRuntimeApp(
|
return await createRuntimeApp(
|
||||||
{
|
{
|
||||||
...config,
|
|
||||||
serveStatic: serveStatic({ root }),
|
serveStatic: serveStatic({ root }),
|
||||||
|
...config,
|
||||||
},
|
},
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
args ?? { env: process.env },
|
args ?? { env: process.env },
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import { readFile, readdir, stat, unlink, writeFile } from "node:fs/promises";
|
import { readFile, readdir, stat, unlink, writeFile } from "node:fs/promises";
|
||||||
import { isFile } from "bknd/utils";
|
import type { FileBody, FileListObject, FileMeta, FileUploadPayload } from "bknd";
|
||||||
import type { FileBody, FileListObject, FileMeta, FileUploadPayload } from "bknd/media";
|
import { StorageAdapter, guessMimeType } from "bknd";
|
||||||
import { StorageAdapter, guessMimeType as guess } from "bknd/media";
|
import { parse, s, isFile } from "bknd/utils";
|
||||||
import { parse, s } from "core/object/schema";
|
|
||||||
|
|
||||||
export const localAdapterConfig = s.object(
|
export const localAdapterConfig = s.object(
|
||||||
{
|
{
|
||||||
@@ -84,7 +83,7 @@ export class StorageLocalAdapter extends StorageAdapter {
|
|||||||
async getObject(key: string, headers: Headers): Promise<Response> {
|
async getObject(key: string, headers: Headers): Promise<Response> {
|
||||||
try {
|
try {
|
||||||
const content = await readFile(`${this.config.path}/${key}`);
|
const content = await readFile(`${this.config.path}/${key}`);
|
||||||
const mimeType = guess(key);
|
const mimeType = guessMimeType(key);
|
||||||
|
|
||||||
return new Response(content, {
|
return new Response(content, {
|
||||||
status: 200,
|
status: 200,
|
||||||
@@ -106,7 +105,7 @@ export class StorageLocalAdapter extends StorageAdapter {
|
|||||||
async getObjectMeta(key: string): Promise<FileMeta> {
|
async getObjectMeta(key: string): Promise<FileMeta> {
|
||||||
const stats = await stat(`${this.config.path}/${key}`);
|
const stats = await stat(`${this.config.path}/${key}`);
|
||||||
return {
|
return {
|
||||||
type: guess(key) || "application/octet-stream",
|
type: guessMimeType(key) || "application/octet-stream",
|
||||||
size: stats.size,
|
size: stats.size,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { Connection } from "bknd/data";
|
import type { Connection } from "bknd";
|
||||||
import { bunSqlite } from "../bun/connection/BunSqliteConnection";
|
import { bunSqlite } from "../bun/connection/BunSqliteConnection";
|
||||||
|
|
||||||
export function sqlite(config?: { url: string }): Connection {
|
export function sqlite(config?: { url: string }): Connection {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { type Connection, libsql } from "bknd/data";
|
import { type Connection, libsql } from "bknd";
|
||||||
|
|
||||||
export function sqlite(config: { url: string }): Connection {
|
export function sqlite(config: { url: string }): Connection {
|
||||||
return libsql(config);
|
return libsql(config);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { Connection } from "bknd/data";
|
import type { Connection } from "bknd";
|
||||||
import { nodeSqlite } from "../node/connection/NodeSqliteConnection";
|
import { nodeSqlite } from "../node/connection/NodeSqliteConnection";
|
||||||
|
|
||||||
export function sqlite(config?: { url: string }): Connection {
|
export function sqlite(config?: { url: string }): Connection {
|
||||||
|
|||||||
+18
-9
@@ -1,18 +1,26 @@
|
|||||||
import { Authenticator, AuthPermissions, Role, type Strategy } from "auth";
|
import type { DB } from "bknd";
|
||||||
import type { PasswordStrategy } from "auth/authenticate/strategies";
|
import * as AuthPermissions from "auth/auth-permissions";
|
||||||
import type { DB } from "core";
|
import type { AuthStrategy } from "auth/authenticate/strategies/Strategy";
|
||||||
|
import type { PasswordStrategy } from "auth/authenticate/strategies/PasswordStrategy";
|
||||||
import { $console, secureRandomString, transformObject } from "core/utils";
|
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 { em, entity, enumm, type FieldSchema } from "data/prototype";
|
||||||
import { Module } from "modules/Module";
|
import { Module } from "modules/Module";
|
||||||
import { AuthController } from "./api/AuthController";
|
import { AuthController } from "./api/AuthController";
|
||||||
import { type AppAuthSchema, authConfigSchema, STRATEGIES } from "./auth-schema";
|
import {
|
||||||
|
type AppAuthSchema,
|
||||||
|
authConfigSchema,
|
||||||
|
AuthStrategyRegistry,
|
||||||
|
buildAuthSchema,
|
||||||
|
} from "./auth-schema";
|
||||||
import { AppUserPool } from "auth/AppUserPool";
|
import { AppUserPool } from "auth/AppUserPool";
|
||||||
import type { AppEntity } from "core/config";
|
import type { AppEntity } from "core/config";
|
||||||
import { usersFields } from "./auth-entities";
|
import { usersFields } from "./auth-entities";
|
||||||
|
import { Authenticator } from "./authenticate/Authenticator";
|
||||||
|
import { Role } from "./authorize/Role";
|
||||||
|
|
||||||
export type UserFieldSchema = FieldSchema<typeof AppAuth.usersFields>;
|
export type UserFieldSchema = FieldSchema<typeof AppAuth.usersFields>;
|
||||||
declare module "core" {
|
declare module "bknd" {
|
||||||
interface Users extends AppEntity, UserFieldSchema {}
|
interface Users extends AppEntity, UserFieldSchema {}
|
||||||
interface DB {
|
interface DB {
|
||||||
users: Users;
|
users: Users;
|
||||||
@@ -65,7 +73,8 @@ export class AppAuth extends Module<AppAuthSchema> {
|
|||||||
// build strategies
|
// build strategies
|
||||||
const strategies = transformObject(this.config.strategies ?? {}, (strategy, name) => {
|
const strategies = transformObject(this.config.strategies ?? {}, (strategy, name) => {
|
||||||
try {
|
try {
|
||||||
return new STRATEGIES[strategy.type].cls(strategy.config as any);
|
const cls = AuthStrategyRegistry.get(strategy.type as any).cls;
|
||||||
|
return new cls(strategy.config as any);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Could not build strategy ${String(
|
`Could not build strategy ${String(
|
||||||
@@ -88,7 +97,7 @@ export class AppAuth extends Module<AppAuthSchema> {
|
|||||||
this.ctx.guard.registerPermissions(AuthPermissions);
|
this.ctx.guard.registerPermissions(AuthPermissions);
|
||||||
}
|
}
|
||||||
|
|
||||||
isStrategyEnabled(strategy: Strategy | string) {
|
isStrategyEnabled(strategy: AuthStrategy | string) {
|
||||||
const name = typeof strategy === "string" ? strategy : strategy.getName();
|
const name = typeof strategy === "string" ? strategy : strategy.getName();
|
||||||
// for now, password is always active
|
// for now, password is always active
|
||||||
if (name === "password") return true;
|
if (name === "password") return true;
|
||||||
@@ -105,7 +114,7 @@ export class AppAuth extends Module<AppAuthSchema> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getSchema() {
|
getSchema() {
|
||||||
return authConfigSchema;
|
return buildAuthSchema();
|
||||||
}
|
}
|
||||||
|
|
||||||
get authenticator(): Authenticator {
|
get authenticator(): Authenticator {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { AuthActionResponse } from "auth/api/AuthController";
|
import type { AuthActionResponse } from "auth/api/AuthController";
|
||||||
import type { AppAuthSchema } from "auth/auth-schema";
|
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";
|
import { type BaseModuleApiOptions, ModuleApi } from "modules/ModuleApi";
|
||||||
|
|
||||||
export type AuthApiOptions = BaseModuleApiOptions & {
|
export type AuthApiOptions = BaseModuleApiOptions & {
|
||||||
@@ -39,7 +39,7 @@ export class AuthApi extends ModuleApi<AuthApiOptions> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async actionSchema(strategy: string, action: string) {
|
async actionSchema(strategy: string, action: string) {
|
||||||
return this.get<Strategy>([strategy, "actions", action, "schema.json"]);
|
return this.get<AuthStrategy>([strategy, "actions", action, "schema.json"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
async action(strategy: string, action: string, input: any) {
|
async action(strategy: string, action: string, input: any) {
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
import { type AppAuth, AuthPermissions, type SafeUser, type Strategy } from "auth";
|
import type { SafeUser } from "bknd";
|
||||||
import { transformObject } from "core/utils";
|
import type { AuthStrategy } from "auth/authenticate/strategies/Strategy";
|
||||||
import { DataPermissions } from "data";
|
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 type { Hono } from "hono";
|
||||||
import { Controller, type ServerEnv } from "modules/Controller";
|
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 = {
|
export type AuthActionResponse = {
|
||||||
success: boolean;
|
success: boolean;
|
||||||
@@ -30,7 +32,7 @@ export class AuthController extends Controller {
|
|||||||
return this.em.repo(entity_name as "users");
|
return this.em.repo(entity_name as "users");
|
||||||
}
|
}
|
||||||
|
|
||||||
private registerStrategyActions(strategy: Strategy, mainHono: Hono<ServerEnv>) {
|
private registerStrategyActions(strategy: AuthStrategy, mainHono: Hono<ServerEnv>) {
|
||||||
if (!this.auth.isStrategyEnabled(strategy)) {
|
if (!this.auth.isStrategyEnabled(strategy)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Permission } from "core";
|
import { Permission } from "core/security/Permission";
|
||||||
|
|
||||||
export const createUser = new Permission("auth.user.create");
|
export const createUser = new Permission("auth.user.create");
|
||||||
//export const updateUser = new Permission("auth.user.update");
|
//export const updateUser = new Permission("auth.user.update");
|
||||||
|
|||||||
+61
-59
@@ -1,41 +1,26 @@
|
|||||||
|
import { Registry, type Constructor } from "core/registry/Registry";
|
||||||
import { cookieConfig, jwtConfig } from "auth/authenticate/Authenticator";
|
import { cookieConfig, jwtConfig } from "auth/authenticate/Authenticator";
|
||||||
import { CustomOAuthStrategy, OAuthStrategy, PasswordStrategy } from "auth/authenticate/strategies";
|
import { objectTransform, s } from "bknd/utils";
|
||||||
import { objectTransform } from "core/utils";
|
import type { ProvidedOAuthConfig } from "./authenticate/strategies/oauth/OAuthStrategy";
|
||||||
import { s } from "core/object/schema";
|
import type { OAuthConfigCustom } from "./authenticate/strategies/oauth/CustomOAuthStrategy";
|
||||||
|
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 Strategies = {
|
export const AuthStrategyRegistry = new Registry<{
|
||||||
password: {
|
cls: Constructor<AuthStrategy>;
|
||||||
cls: PasswordStrategy,
|
schema: s.Schema;
|
||||||
schema: PasswordStrategy.prototype.getSchema(),
|
}>((cls: Constructor<AuthStrategy>) => ({
|
||||||
},
|
cls,
|
||||||
oauth: {
|
schema: cls.prototype.getSchema() as s.Schema,
|
||||||
cls: OAuthStrategy,
|
}))
|
||||||
schema: OAuthStrategy.prototype.getSchema(),
|
.register("password", PasswordStrategy)
|
||||||
},
|
.register("oauth", OAuthStrategy)
|
||||||
custom_oauth: {
|
.register("custom_oauth", CustomOAuthStrategy);
|
||||||
cls: CustomOAuthStrategy,
|
|
||||||
schema: CustomOAuthStrategy.prototype.getSchema(),
|
|
||||||
},
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
export const STRATEGIES = Strategies;
|
export type AppAuthOAuthStrategy = ProvidedOAuthConfig;
|
||||||
const strategiesSchemaObject = objectTransform(STRATEGIES, (strategy, name) => {
|
export type AppAuthCustomOAuthStrategy = OAuthConfigCustom;
|
||||||
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<typeof strategiesSchema>;
|
|
||||||
export type AppAuthOAuthStrategy = s.Static<typeof STRATEGIES.oauth.schema>;
|
|
||||||
export type AppAuthCustomOAuthStrategy = s.Static<typeof STRATEGIES.custom_oauth.schema>;
|
|
||||||
|
|
||||||
const guardConfigSchema = s.object({
|
const guardConfigSchema = s.object({
|
||||||
enabled: s.boolean({ default: false }).optional(),
|
enabled: s.boolean({ default: false }).optional(),
|
||||||
@@ -46,32 +31,49 @@ export const guardRoleSchema = s.strictObject({
|
|||||||
implicit_allow: s.boolean().optional(),
|
implicit_allow: s.boolean().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const authConfigSchema = s.strictObject(
|
export function buildAuthSchema() {
|
||||||
{
|
const strategiesSchemaObject = objectTransform(AuthStrategyRegistry.all(), (strategy, name) => {
|
||||||
enabled: s.boolean({ default: false }),
|
return s.strictObject(
|
||||||
basepath: s.string({ default: "/api/auth" }),
|
{
|
||||||
entity_name: s.string({ default: "users" }),
|
enabled: s.boolean({ default: true }).optional(),
|
||||||
allow_register: s.boolean({ default: true }).optional(),
|
type: s.literal(name),
|
||||||
jwt: jwtConfig,
|
config: strategy.schema,
|
||||||
cookie: cookieConfig,
|
},
|
||||||
strategies: s.record(strategiesSchema, {
|
{
|
||||||
title: "Strategies",
|
title: name,
|
||||||
default: {
|
},
|
||||||
password: {
|
);
|
||||||
type: "password",
|
});
|
||||||
enabled: true,
|
|
||||||
config: {
|
const strategiesSchema = s.anyOf(Object.values(strategiesSchemaObject));
|
||||||
hashing: "sha256",
|
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(),
|
||||||
guard: guardConfigSchema.optional(),
|
roles: s.record(guardRoleSchema, { default: {} }).optional(),
|
||||||
roles: s.record(guardRoleSchema, { default: {} }).optional(),
|
},
|
||||||
},
|
{ title: "Authentication" },
|
||||||
{ title: "Authentication" },
|
);
|
||||||
);
|
}
|
||||||
|
export const authConfigSchema = buildAuthSchema();
|
||||||
|
|
||||||
|
export type AppAuthStrategies = s.Static<(typeof authConfigSchema)["properties"]["strategies"]>;
|
||||||
export type AppAuthJWTConfig = s.Static<typeof jwtConfig>;
|
export type AppAuthJWTConfig = s.Static<typeof jwtConfig>;
|
||||||
|
|
||||||
export type AppAuthSchema = s.Static<typeof authConfigSchema>;
|
export type AppAuthSchema = s.Static<typeof authConfigSchema>;
|
||||||
|
|||||||
@@ -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 { addFlashMessage } from "core/server/flash";
|
||||||
import { runtimeSupports, truncate, $console } from "core/utils";
|
import type { Context } from "hono";
|
||||||
import type { Context, Hono } from "hono";
|
|
||||||
import { deleteCookie, getSignedCookie, setSignedCookie } from "hono/cookie";
|
import { deleteCookie, getSignedCookie, setSignedCookie } from "hono/cookie";
|
||||||
import { sign, verify } from "hono/jwt";
|
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 type { ServerEnv } from "modules/Controller";
|
||||||
import { pick } from "lodash-es";
|
import { pick } from "lodash-es";
|
||||||
import { InvalidConditionsException } from "auth/errors";
|
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
|
type Input = any; // workaround
|
||||||
export type JWTPayload = Parameters<typeof sign>[0];
|
export type JWTPayload = Parameters<typeof sign>[0];
|
||||||
@@ -21,17 +22,6 @@ export type StrategyAction<S extends s.ObjectSchema = s.ObjectSchema> = {
|
|||||||
};
|
};
|
||||||
export type StrategyActions = Partial<Record<StrategyActionName, StrategyAction>>;
|
export type StrategyActions = Partial<Record<StrategyActionName, StrategyAction>>;
|
||||||
|
|
||||||
// @todo: add schema to interface to ensure proper inference
|
|
||||||
// @todo: add tests (e.g. invalid strategy_value)
|
|
||||||
export interface Strategy {
|
|
||||||
getController: (auth: Authenticator) => Hono<any>;
|
|
||||||
getType: () => string;
|
|
||||||
getMode: () => "form" | "external";
|
|
||||||
getName: () => string;
|
|
||||||
toJSON: (secrets?: boolean) => any;
|
|
||||||
getActions?: () => StrategyActions;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type User = DB["users"];
|
export type User = DB["users"];
|
||||||
|
|
||||||
export type ProfileExchange = {
|
export type ProfileExchange = {
|
||||||
@@ -58,6 +48,7 @@ export const cookieConfig = s
|
|||||||
secure: s.boolean({ default: true }),
|
secure: s.boolean({ default: true }),
|
||||||
httpOnly: s.boolean({ default: true }),
|
httpOnly: s.boolean({ default: true }),
|
||||||
expires: s.number({ default: defaultCookieExpires }), // seconds
|
expires: s.number({ default: defaultCookieExpires }), // seconds
|
||||||
|
partitioned: s.boolean({ default: false }),
|
||||||
renew: s.boolean({ default: true }),
|
renew: s.boolean({ default: true }),
|
||||||
pathSuccess: s.string({ default: "/" }),
|
pathSuccess: s.string({ default: "/" }),
|
||||||
pathLoggedOut: s.string({ default: "/" }),
|
pathLoggedOut: s.string({ default: "/" }),
|
||||||
@@ -97,7 +88,7 @@ export type AuthResolveOptions = {
|
|||||||
};
|
};
|
||||||
export type AuthUserResolver = (
|
export type AuthUserResolver = (
|
||||||
action: AuthAction,
|
action: AuthAction,
|
||||||
strategy: Strategy,
|
strategy: AuthStrategy,
|
||||||
profile: ProfileExchange,
|
profile: ProfileExchange,
|
||||||
opts?: AuthResolveOptions,
|
opts?: AuthResolveOptions,
|
||||||
) => Promise<ProfileExchange | undefined>;
|
) => Promise<ProfileExchange | undefined>;
|
||||||
@@ -107,7 +98,9 @@ type AuthClaims = SafeUser & {
|
|||||||
exp?: number;
|
exp?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export class Authenticator<Strategies extends Record<string, Strategy> = Record<string, Strategy>> {
|
export class Authenticator<
|
||||||
|
Strategies extends Record<string, AuthStrategy> = Record<string, AuthStrategy>,
|
||||||
|
> {
|
||||||
private readonly config: AuthConfig;
|
private readonly config: AuthConfig;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@@ -120,7 +113,7 @@ export class Authenticator<Strategies extends Record<string, Strategy> = Record<
|
|||||||
|
|
||||||
async resolveLogin(
|
async resolveLogin(
|
||||||
c: Context,
|
c: Context,
|
||||||
strategy: Strategy,
|
strategy: AuthStrategy,
|
||||||
profile: Partial<SafeUser>,
|
profile: Partial<SafeUser>,
|
||||||
verify: (user: User) => Promise<void>,
|
verify: (user: User) => Promise<void>,
|
||||||
opts?: AuthResolveOptions,
|
opts?: AuthResolveOptions,
|
||||||
@@ -158,7 +151,7 @@ export class Authenticator<Strategies extends Record<string, Strategy> = Record<
|
|||||||
|
|
||||||
async resolveRegister(
|
async resolveRegister(
|
||||||
c: Context,
|
c: Context,
|
||||||
strategy: Strategy,
|
strategy: AuthStrategy,
|
||||||
profile: CreateUser,
|
profile: CreateUser,
|
||||||
verify: (user: User) => Promise<void>,
|
verify: (user: User) => Promise<void>,
|
||||||
opts?: AuthResolveOptions,
|
opts?: AuthResolveOptions,
|
||||||
@@ -217,7 +210,7 @@ export class Authenticator<Strategies extends Record<string, Strategy> = Record<
|
|||||||
|
|
||||||
await addFlashMessage(c, String(error), "error");
|
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);
|
return c.redirect(referer);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -227,7 +220,7 @@ export class Authenticator<Strategies extends Record<string, Strategy> = Record<
|
|||||||
|
|
||||||
strategy<
|
strategy<
|
||||||
StrategyName extends keyof Strategies,
|
StrategyName extends keyof Strategies,
|
||||||
Strat extends Strategy = Strategies[StrategyName],
|
Strat extends AuthStrategy = Strategies[StrategyName],
|
||||||
>(strategy: StrategyName): Strat {
|
>(strategy: StrategyName): Strat {
|
||||||
try {
|
try {
|
||||||
return this.strategies[strategy] as unknown as Strat;
|
return this.strategies[strategy] as unknown as Strat;
|
||||||
@@ -334,6 +327,11 @@ export class Authenticator<Strategies extends Record<string, Strategy> = Record<
|
|||||||
await setSignedCookie(c, "auth", token, secret, this.cookieOptions);
|
await setSignedCookie(c, "auth", token, secret, this.cookieOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async unsafeGetAuthCookie(token: string): Promise<string | undefined> {
|
||||||
|
// 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) {
|
private deleteAuthCookie(c: Context) {
|
||||||
$console.debug("deleting auth cookie");
|
$console.debug("deleting auth cookie");
|
||||||
deleteCookie(c, "auth", this.cookieOptions);
|
deleteCookie(c, "auth", this.cookieOptions);
|
||||||
|
|||||||
@@ -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 { hash, $console } from "core/utils";
|
||||||
import { Hono } from "hono";
|
import { Hono } from "hono";
|
||||||
import { compare as bcryptCompare, genSalt as bcryptGenSalt, hash as bcryptHash } from "bcryptjs";
|
import { compare as bcryptCompare, genSalt as bcryptGenSalt, hash as bcryptHash } from "bcryptjs";
|
||||||
import { Strategy } from "./Strategy";
|
import { AuthStrategy } from "./Strategy";
|
||||||
import { s, parse, jsc } from "core/object/schema";
|
import { s, parse, jsc } from "bknd/utils";
|
||||||
|
|
||||||
const schema = s
|
const schema = s
|
||||||
.object({
|
.object({
|
||||||
@@ -14,7 +16,7 @@ const schema = s
|
|||||||
|
|
||||||
export type PasswordStrategyOptions = s.Static<typeof schema>;
|
export type PasswordStrategyOptions = s.Static<typeof schema>;
|
||||||
|
|
||||||
export class PasswordStrategy extends Strategy<typeof schema> {
|
export class PasswordStrategy extends AuthStrategy<typeof schema> {
|
||||||
constructor(config: Partial<PasswordStrategyOptions> = {}) {
|
constructor(config: Partial<PasswordStrategyOptions> = {}) {
|
||||||
super(config as any, "password", "password", "form");
|
super(config as any, "password", "password", "form");
|
||||||
|
|
||||||
@@ -33,7 +35,7 @@ export class PasswordStrategy extends Strategy<typeof schema> {
|
|||||||
private getPayloadSchema() {
|
private getPayloadSchema() {
|
||||||
return s.object({
|
return s.object({
|
||||||
email: s.string({
|
email: s.string({
|
||||||
pattern: /^[\w-\.\+_]+@([\w-]+\.)+[\w-]{2,4}$/,
|
format: "email",
|
||||||
}),
|
}),
|
||||||
password: s.string({
|
password: s.string({
|
||||||
minLength: 8, // @todo: this should be configurable
|
minLength: 8, // @todo: this should be configurable
|
||||||
|
|||||||
@@ -5,11 +5,11 @@ import type {
|
|||||||
StrategyActions,
|
StrategyActions,
|
||||||
} from "../Authenticator";
|
} from "../Authenticator";
|
||||||
import type { Hono } from "hono";
|
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 type StrategyMode = "form" | "external";
|
||||||
|
|
||||||
export abstract class Strategy<Schema extends s.Schema = s.Schema> {
|
export abstract class AuthStrategy<Schema extends s.Schema = s.Schema> {
|
||||||
protected actions: StrategyActions = {};
|
protected actions: StrategyActions = {};
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type * as oauth from "oauth4webapi";
|
import type * as oauth from "oauth4webapi";
|
||||||
import { OAuthStrategy } from "./OAuthStrategy";
|
import { OAuthStrategy } from "./OAuthStrategy";
|
||||||
import { s } from "core/object/schema";
|
import { s } from "bknd/utils";
|
||||||
|
|
||||||
type SupportedTypes = "oauth2" | "oidc";
|
type SupportedTypes = "oauth2" | "oidc";
|
||||||
|
|
||||||
@@ -30,7 +30,7 @@ const oauthSchemaCustom = s.strictObject(
|
|||||||
{ title: "Custom OAuth" },
|
{ title: "Custom OAuth" },
|
||||||
);
|
);
|
||||||
|
|
||||||
type OAuthConfigCustom = s.Static<typeof oauthSchemaCustom>;
|
export type OAuthConfigCustom = s.Static<typeof oauthSchemaCustom>;
|
||||||
|
|
||||||
export type UserProfile = {
|
export type UserProfile = {
|
||||||
sub: string;
|
sub: string;
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import type { AuthAction, Authenticator } from "auth";
|
import type { Authenticator, AuthAction } from "auth/authenticate/Authenticator";
|
||||||
import { Exception, isDebug } from "core";
|
|
||||||
import { filterKeys } from "core/utils";
|
|
||||||
import { type Context, Hono } from "hono";
|
import { type Context, Hono } from "hono";
|
||||||
import { getSignedCookie, setSignedCookie } from "hono/cookie";
|
import { getSignedCookie, setSignedCookie } from "hono/cookie";
|
||||||
import * as oauth from "oauth4webapi";
|
import * as oauth from "oauth4webapi";
|
||||||
import * as issuers from "./issuers";
|
import * as issuers from "./issuers";
|
||||||
import { Strategy } from "auth/authenticate/strategies/Strategy";
|
import { s, filterKeys } from "bknd/utils";
|
||||||
import { s } from "core/object/schema";
|
import { Exception } from "core/errors";
|
||||||
|
import { isDebug } from "core/env";
|
||||||
|
import { AuthStrategy } from "../Strategy";
|
||||||
|
|
||||||
type ConfiguredIssuers = keyof typeof issuers;
|
type ConfiguredIssuers = keyof typeof issuers;
|
||||||
type SupportedTypes = "oauth2" | "oidc";
|
type SupportedTypes = "oauth2" | "oidc";
|
||||||
@@ -26,7 +26,7 @@ const schemaProvided = s.object(
|
|||||||
},
|
},
|
||||||
{ title: "OAuth" },
|
{ title: "OAuth" },
|
||||||
);
|
);
|
||||||
type ProvidedOAuthConfig = s.Static<typeof schemaProvided>;
|
export type ProvidedOAuthConfig = s.Static<typeof schemaProvided>;
|
||||||
|
|
||||||
export type CustomOAuthConfig = {
|
export type CustomOAuthConfig = {
|
||||||
type: SupportedTypes;
|
type: SupportedTypes;
|
||||||
@@ -70,7 +70,7 @@ export class OAuthCallbackException extends Exception {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class OAuthStrategy extends Strategy<typeof schemaProvided> {
|
export class OAuthStrategy extends AuthStrategy<typeof schemaProvided> {
|
||||||
constructor(config: ProvidedOAuthConfig) {
|
constructor(config: ProvidedOAuthConfig) {
|
||||||
super(config, "oauth", config.name, "external");
|
super(config, "oauth", config.name, "external");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Exception, Permission } from "core";
|
import { Exception } from "core/errors";
|
||||||
import { $console, objectTransform } from "core/utils";
|
import { $console, objectTransform } from "core/utils";
|
||||||
|
import { Permission } from "core/security/Permission";
|
||||||
import type { Context } from "hono";
|
import type { Context } from "hono";
|
||||||
import type { ServerEnv } from "modules/Controller";
|
import type { ServerEnv } from "modules/Controller";
|
||||||
import { Role } from "./Role";
|
import { Role } from "./Role";
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Permission } from "core";
|
import { Permission } from "core/security/Permission";
|
||||||
|
|
||||||
export class RolePermission {
|
export class RolePermission {
|
||||||
constructor(
|
constructor(
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Exception, isDebug } from "core";
|
import { Exception } from "core/errors";
|
||||||
import { HttpStatus } from "core/utils";
|
import { isDebug } from "core/env";
|
||||||
|
import { HttpStatus } from "bknd/utils";
|
||||||
|
|
||||||
export class AuthException extends Exception {
|
export class AuthException extends Exception {
|
||||||
getSafeErrorAndCode() {
|
getSafeErrorAndCode() {
|
||||||
|
|||||||
@@ -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";
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { Permission } from "core";
|
import type { Permission } from "core/security/Permission";
|
||||||
import { $console, patternMatch } from "core/utils";
|
import { $console, patternMatch } from "bknd/utils";
|
||||||
import type { Context } from "hono";
|
import type { Context } from "hono";
|
||||||
import { createMiddleware } from "hono/factory";
|
import { createMiddleware } from "hono/factory";
|
||||||
import type { ServerEnv } from "modules/Controller";
|
import type { ServerEnv } from "modules/Controller";
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import type { CliCommand } from "cli/types";
|
|||||||
import { typewriter, wait } from "cli/utils/cli";
|
import { typewriter, wait } from "cli/utils/cli";
|
||||||
import { execAsync, getVersion } from "cli/utils/sys";
|
import { execAsync, getVersion } from "cli/utils/sys";
|
||||||
import { Option } from "commander";
|
import { Option } from "commander";
|
||||||
import { env } from "core";
|
import { env } from "bknd";
|
||||||
import color from "picocolors";
|
import color from "picocolors";
|
||||||
import { overridePackageJson, updateBkndPackages } from "./npm";
|
import { overridePackageJson, updateBkndPackages } from "./npm";
|
||||||
import { type Template, templates, type TemplateSetupCtx } from "./templates";
|
import { type Template, templates, type TemplateSetupCtx } from "./templates";
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import type { Config } from "@libsql/client/node";
|
import type { Config } from "@libsql/client/node";
|
||||||
import type { App, CreateAppConfig } from "App";
|
|
||||||
import { StorageLocalAdapter } from "adapter/node/storage";
|
import { StorageLocalAdapter } from "adapter/node/storage";
|
||||||
import type { CliBkndConfig, CliCommand } from "cli/types";
|
import type { CliBkndConfig, CliCommand } from "cli/types";
|
||||||
import { Option } from "commander";
|
import { Option } from "commander";
|
||||||
import { config } from "core";
|
import { config, type App, type CreateAppConfig } from "bknd";
|
||||||
import dotenv from "dotenv";
|
import dotenv from "dotenv";
|
||||||
import { registries } from "modules/registries";
|
import { registries } from "modules/registries";
|
||||||
import c from "picocolors";
|
import c from "picocolors";
|
||||||
@@ -16,8 +15,8 @@ import {
|
|||||||
serveStatic,
|
serveStatic,
|
||||||
startServer,
|
startServer,
|
||||||
} from "./platform";
|
} from "./platform";
|
||||||
import { createRuntimeApp, makeConfig } from "adapter";
|
import { createRuntimeApp, makeConfig } from "bknd/adapter";
|
||||||
import { colorizeConsole, isBun } from "core/utils";
|
import { colorizeConsole, isBun } from "bknd/utils";
|
||||||
|
|
||||||
const env_files = [".env", ".dev.vars"];
|
const env_files = [".env", ".dev.vars"];
|
||||||
dotenv.config({
|
dotenv.config({
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { PostHog } from "posthog-js-lite";
|
import { PostHog } from "posthog-js-lite";
|
||||||
import { getVersion } from "cli/utils/sys";
|
import { getVersion } from "cli/utils/sys";
|
||||||
import { env, isDebug } from "core";
|
import { env, isDebug } from "bknd";
|
||||||
import { $console } from "core/utils";
|
import { $console } from "bknd/utils";
|
||||||
|
|
||||||
type Properties = { [p: string]: any };
|
type Properties = { [p: string]: any };
|
||||||
|
|
||||||
|
|||||||
@@ -6,23 +6,3 @@ export interface IEmailDriver<Data = unknown, Options = object> {
|
|||||||
options?: Options,
|
options?: Options,
|
||||||
): Promise<Data>;
|
): Promise<Data>;
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
|
||||||
|
|||||||
@@ -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 } 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<any, any, any>;
|
|
||||||
export interface ClassController {
|
|
||||||
getController: () => Hono<any, any, any>;
|
|
||||||
getMiddleware?: MiddlewareHandler<any, any, any>;
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
import { get, has, omit, set } from "lodash-es";
|
import { get, has, omit, set } from "lodash-es";
|
||||||
import { getFullPathKeys, mergeObjectWith } from "../utils";
|
import { type s, parse, stripMark, getFullPathKeys, mergeObjectWith, deepFreeze } from "bknd/utils";
|
||||||
import { type s, parse, stripMark } from "core/object/schema";
|
|
||||||
|
|
||||||
export type SchemaObjectOptions<Schema extends s.Schema> = {
|
export type SchemaObjectOptions<Schema extends s.Schema> = {
|
||||||
onUpdate?: (config: s.Static<Schema>) => void | Promise<void>;
|
onUpdate?: (config: s.Static<Schema>) => void | Promise<void>;
|
||||||
@@ -26,14 +25,16 @@ export class SchemaObject<Schema extends TSchema = TSchema> {
|
|||||||
initial?: Partial<s.Static<Schema>>,
|
initial?: Partial<s.Static<Schema>>,
|
||||||
private options?: SchemaObjectOptions<Schema>,
|
private options?: SchemaObjectOptions<Schema>,
|
||||||
) {
|
) {
|
||||||
this._default = _schema.template({}, { withOptional: true }) as any;
|
this._default = deepFreeze(_schema.template({}, { withOptional: true }) as any);
|
||||||
this._value = parse(_schema, structuredClone(initial ?? {}), {
|
this._value = deepFreeze(
|
||||||
withDefaults: true,
|
parse(_schema, structuredClone(initial ?? {}), {
|
||||||
withExtendedDefaults: true,
|
withDefaults: true,
|
||||||
forceParse: this.isForceParse(),
|
withExtendedDefaults: true,
|
||||||
skipMark: this.isForceParse(),
|
forceParse: this.isForceParse(),
|
||||||
});
|
skipMark: this.isForceParse(),
|
||||||
this._config = Object.freeze(this._value);
|
}),
|
||||||
|
);
|
||||||
|
this._config = deepFreeze(this._value);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected isForceParse(): boolean {
|
protected isForceParse(): boolean {
|
||||||
@@ -76,8 +77,8 @@ export class SchemaObject<Schema extends TSchema = TSchema> {
|
|||||||
// regardless of "noEmit" – this should always be triggered
|
// regardless of "noEmit" – this should always be triggered
|
||||||
const updatedConfig = await this.onBeforeUpdate(this._config, valid);
|
const updatedConfig = await this.onBeforeUpdate(this._config, valid);
|
||||||
|
|
||||||
this._value = updatedConfig;
|
this._value = deepFreeze(updatedConfig);
|
||||||
this._config = Object.freeze(updatedConfig);
|
this._config = deepFreeze(updatedConfig);
|
||||||
|
|
||||||
if (noEmit !== true) {
|
if (noEmit !== true) {
|
||||||
await this.options?.onUpdate?.(this._config);
|
await this.options?.onUpdate?.(this._config);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { PrimaryFieldType } from "core";
|
import type { PrimaryFieldType } from "core/config";
|
||||||
|
|
||||||
export type Primitive = PrimaryFieldType | string | number | boolean;
|
export type Primitive = PrimaryFieldType | string | number | boolean;
|
||||||
export function isPrimitive(value: any): value is Primitive {
|
export function isPrimitive(value: any): value is Primitive {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
export type Constructor<T> = new (...args: any[]) => T;
|
export type Constructor<T> = new (...args: any[]) => T;
|
||||||
|
export type ClassThatImplements<T> = Constructor<T> & { prototype: T };
|
||||||
export type RegisterFn<Item> = (unknown: any) => Item;
|
export type RegisterFn<Item> = (unknown: any) => Item;
|
||||||
|
|
||||||
export class Registry<
|
export class Registry<
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { describe, expect, test } from "bun:test";
|
import { describe, expect, test } from "bun:test";
|
||||||
import { SimpleRenderer } from "core";
|
import { SimpleRenderer } from "./SimpleRenderer";
|
||||||
|
|
||||||
describe(SimpleRenderer, () => {
|
describe(SimpleRenderer, () => {
|
||||||
const renderer = new SimpleRenderer(
|
const renderer = new SimpleRenderer(
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { datetimeStringLocal } from "core/utils";
|
import { datetimeStringLocal } from "./dates";
|
||||||
import colors from "picocolors";
|
import colors from "picocolors";
|
||||||
import { env } from "core";
|
import { env } from "core/env";
|
||||||
|
|
||||||
function hasColors() {
|
function hasColors() {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -13,3 +13,18 @@ export * from "./uuid";
|
|||||||
export * from "./test";
|
export * from "./test";
|
||||||
export * from "./runtime";
|
export * from "./runtime";
|
||||||
export * from "./numbers";
|
export * from "./numbers";
|
||||||
|
export {
|
||||||
|
s,
|
||||||
|
stripMark,
|
||||||
|
mark,
|
||||||
|
stringIdentifier,
|
||||||
|
SecretSchema,
|
||||||
|
secret,
|
||||||
|
parse,
|
||||||
|
jsc,
|
||||||
|
describeRoute,
|
||||||
|
schemaToSpec,
|
||||||
|
openAPISpecs,
|
||||||
|
type ParseOptions,
|
||||||
|
InvalidSchemaError,
|
||||||
|
} from "./schema";
|
||||||
|
|||||||
@@ -94,16 +94,14 @@ export function transformObject<T extends Record<string, any>, U>(
|
|||||||
object: T,
|
object: T,
|
||||||
transform: (value: T[keyof T], key: keyof T) => U | undefined,
|
transform: (value: T[keyof T], key: keyof T) => U | undefined,
|
||||||
): { [K in keyof T]: U } {
|
): { [K in keyof T]: U } {
|
||||||
return Object.entries(object).reduce(
|
const result = {} as { [K in keyof T]: U };
|
||||||
(acc, [key, value]) => {
|
for (const [key, value] of Object.entries(object) as [keyof T, T[keyof T]][]) {
|
||||||
const t = transform(value, key as keyof T);
|
const t = transform(value, key);
|
||||||
if (typeof t !== "undefined") {
|
if (typeof t !== "undefined") {
|
||||||
acc[key as keyof T] = t;
|
result[key] = t;
|
||||||
}
|
}
|
||||||
return acc;
|
}
|
||||||
},
|
return result;
|
||||||
{} as { [K in keyof T]: U },
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
export const objectTransform = transformObject;
|
export const objectTransform = transformObject;
|
||||||
|
|
||||||
@@ -419,3 +417,21 @@ export function pick<T extends object, K extends keyof T>(obj: T, keys: K[]): Pi
|
|||||||
{} as Pick<T, K>,
|
{} as Pick<T, K>,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function deepFreeze<T extends object>(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);
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import * as s from "jsonv-ts";
|
|||||||
export { validator as jsc, type Options } from "jsonv-ts/hono";
|
export { validator as jsc, type Options } from "jsonv-ts/hono";
|
||||||
export { describeRoute, schemaToSpec, openAPISpecs } from "jsonv-ts/hono";
|
export { describeRoute, schemaToSpec, openAPISpecs } from "jsonv-ts/hono";
|
||||||
|
|
||||||
export { secret } from "./secret";
|
export { secret, SecretSchema } from "./secret";
|
||||||
|
|
||||||
export { s };
|
export { s };
|
||||||
|
|
||||||
@@ -42,6 +42,7 @@ export type ParseOptions = {
|
|||||||
withDefaults?: boolean;
|
withDefaults?: boolean;
|
||||||
withExtendedDefaults?: boolean;
|
withExtendedDefaults?: boolean;
|
||||||
coerce?: boolean;
|
coerce?: boolean;
|
||||||
|
coerceDropUnknown?: boolean;
|
||||||
clone?: boolean;
|
clone?: boolean;
|
||||||
skipMark?: boolean; // @todo: do something with this
|
skipMark?: boolean; // @todo: do something with this
|
||||||
forceParse?: boolean; // @todo: do something with this
|
forceParse?: boolean; // @todo: do something with this
|
||||||
@@ -59,7 +60,10 @@ export function parse<S extends s.Schema, Options extends ParseOptions = ParseOp
|
|||||||
opts?: Options,
|
opts?: Options,
|
||||||
): Options extends { coerce: true } ? s.StaticCoerced<S> : s.Static<S> {
|
): Options extends { coerce: true } ? s.StaticCoerced<S> : s.Static<S> {
|
||||||
const schema = (opts?.clone ? cloneSchema(_schema as any) : _schema) as s.Schema;
|
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) {
|
if (opts?.withDefaults !== false) {
|
||||||
value = schema.template(value, {
|
value = schema.template(value, {
|
||||||
withOptional: true,
|
withOptional: true,
|
||||||
@@ -1,15 +1,12 @@
|
|||||||
import { transformObject } from "core/utils";
|
import { transformObject } from "core/utils";
|
||||||
import {
|
|
||||||
DataPermissions,
|
|
||||||
type Entity,
|
|
||||||
EntityIndex,
|
|
||||||
type EntityManager,
|
|
||||||
constructEntity,
|
|
||||||
constructRelation,
|
|
||||||
} from "data";
|
|
||||||
import { Module } from "modules/Module";
|
import { Module } from "modules/Module";
|
||||||
import { DataController } from "./api/DataController";
|
import { DataController } from "./api/DataController";
|
||||||
import { type AppDataConfig, dataConfigSchema } from "./data-schema";
|
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<AppDataConfig> {
|
export class AppData extends Module<AppDataConfig> {
|
||||||
override async build() {
|
override async build() {
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import type { DB } from "core";
|
import type { DB, EntityData, RepoQueryIn } from "bknd";
|
||||||
import type { EntityData, RepoQueryIn, RepositoryResultJSON } from "data";
|
|
||||||
import type { Insertable, Selectable, Updateable } from "kysely";
|
import type { Insertable, Selectable, Updateable } from "kysely";
|
||||||
import { type BaseModuleApiOptions, ModuleApi, type PrimaryFieldType } from "modules";
|
import { type BaseModuleApiOptions, ModuleApi, type PrimaryFieldType } from "modules";
|
||||||
import type { FetchPromise, ResponseObject } from "modules/ModuleApi";
|
import type { FetchPromise, ResponseObject } from "modules/ModuleApi";
|
||||||
|
import type { RepositoryResultJSON } from "data/entities/query/RepositoryResult";
|
||||||
|
|
||||||
export type DataApiOptions = BaseModuleApiOptions & {
|
export type DataApiOptions = BaseModuleApiOptions & {
|
||||||
queryLengthLimit: number;
|
queryLengthLimit: number;
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user