Compare commits

..

2 Commits

Author SHA1 Message Date
dswbx 7a046fa18e updated references 2025-07-07 08:39:21 +02:00
dswbx 786d1d1bd4 init media adapter refactoring 2025-07-05 18:12:55 +02:00
122 changed files with 374 additions and 402 deletions
+1 -1
View File
@@ -6,7 +6,7 @@ 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 "bknd/core"; import { parse } from "core/object/schema";
beforeAll(disableConsoleLog); beforeAll(disableConsoleLog);
afterAll(enableConsoleLog); afterAll(enableConsoleLog);
+1 -2
View File
@@ -1,5 +1,4 @@
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 * as proto from "../../src/data/prototype"; import * as proto from "../../src/data/prototype";
import { StorageLocalAdapter } from "adapter/node/storage/StorageLocalAdapter"; import { StorageLocalAdapter } from "adapter/node/storage/StorageLocalAdapter";
@@ -14,8 +13,8 @@ describe("repros", async () => {
* There was an issue that AppData had old configs because of system entity "media" * There was an issue that AppData had old configs because of system entity "media"
*/ */
test("registers media entity correctly to relate to it", async () => { test("registers media entity correctly to relate to it", async () => {
registries.media.register("local", StorageLocalAdapter);
const app = createApp(); const app = createApp();
app.module.media.adapters.set("local", StorageLocalAdapter);
await app.build(); await app.build();
{ {
+1 -1
View File
@@ -2,7 +2,7 @@ import { describe, expect, test } from "bun:test";
import { Authenticator, type User, type UserPool } from "../../src/auth"; import { Authenticator, type User, type UserPool } from "../../src/auth";
import { cookieConfig } from "../../src/auth/authenticate/Authenticator"; import { cookieConfig } from "../../src/auth/authenticate/Authenticator";
import { PasswordStrategy } from "../../src/auth/authenticate/strategies/PasswordStrategy"; import { PasswordStrategy } from "../../src/auth/authenticate/strategies/PasswordStrategy";
import { parse } from "bknd/core"; import { parse } from "core/object/schema";
/*class MemoryUserPool implements UserPool { /*class MemoryUserPool implements UserPool {
constructor(private users: User[] = []) {} constructor(private users: User[] = []) {}
+1 -1
View File
@@ -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";
import { s } from "bknd/core"; import { s } from "core/object/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 { SchemaObject } from "../../../src/core";
import { s } from "bknd/core"; import { s } from "core/object/schema";
describe("SchemaObject", async () => { describe("SchemaObject", async () => {
test("basic", async () => { test("basic", async () => {
+1 -1
View File
@@ -1,7 +1,7 @@
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";
import { parse } from "bknd/core"; import { parse } from "core/object/schema";
import { import {
Entity, Entity,
type EntityData, type EntityData,
+1 -1
View File
@@ -1,5 +1,5 @@
import { afterAll, expect as bunExpect, describe, test } from "bun:test"; import { afterAll, expect as bunExpect, describe, test } from "bun:test";
import { stripMark } from "bknd/core"; import { stripMark } from "core/object/schema";
import { Entity, EntityManager, PolymorphicRelation, TextField } from "../../src/data"; import { Entity, EntityManager, PolymorphicRelation, TextField } from "../../src/data";
import { getDummyConnection } from "./helper"; import { getDummyConnection } from "./helper";
+1 -1
View File
@@ -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 "bknd/core"; import { stripMark } from "core/object/schema";
describe("[data] Field", async () => { describe("[data] Field", async () => {
class FieldSpec extends Field { class FieldSpec extends Field {
@@ -1,6 +1,6 @@
import { describe, expect, test } from "bun:test"; import { describe, expect, test } from "bun:test";
import { Entity, EntityIndex, Field } from "../../../../src/data"; import { Entity, EntityIndex, Field } from "../../../../src/data";
import { s } from "bknd/core"; import { s } from "core/object/schema";
class TestField extends Field { class TestField extends Field {
protected getSchema(): any { protected getSchema(): any {
+1 -1
View File
@@ -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 "bknd/core"; import { s } from "core/object/schema";
export class StringifyTask<Output extends string> extends Task< export class StringifyTask<Output extends string> extends Task<
typeof StringifyTask.schema, typeof StringifyTask.schema,
+1 -1
View File
@@ -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 "bknd/core"; import { s } from "core/object/schema";
describe.skip("Task", async () => { describe.skip("Task", async () => {
test("resolveParams: template with parse", async () => { test("resolveParams: template with parse", async () => {
+1 -1
View File
@@ -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 "bknd/core"; import { s, parse } from "core/object/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";
+1 -1
View File
@@ -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 "bknd/core"; import { s } from "core/object/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 -5
View File
@@ -1,17 +1,12 @@
/// <reference types="@types/bun" /> /// <reference types="@types/bun" />
import { afterAll, beforeAll, describe, expect, test } from "bun:test"; import { afterAll, beforeAll, describe, expect, test } from "bun:test";
import { registries } from "../../src";
import { createApp } from "core/test/utils"; import { createApp } from "core/test/utils";
import { mergeObject, randomString } from "../../src/core/utils"; import { mergeObject, randomString } from "../../src/core/utils";
import type { TAppMediaConfig } from "../../src/media/media-schema"; import type { TAppMediaConfig } from "../../src/media/media-schema";
import { StorageLocalAdapter } from "adapter/node/storage/StorageLocalAdapter"; import { StorageLocalAdapter } from "adapter/node/storage/StorageLocalAdapter";
import { assetsPath, assetsTmpPath, disableConsoleLog, enableConsoleLog } from "../helper"; import { assetsPath, assetsTmpPath, disableConsoleLog, enableConsoleLog } from "../helper";
beforeAll(() => {
registries.media.register("local", StorageLocalAdapter);
});
const path = `${assetsPath}/image.png`; const path = `${assetsPath}/image.png`;
async function makeApp(mediaOverride: Partial<TAppMediaConfig> = {}) { async function makeApp(mediaOverride: Partial<TAppMediaConfig> = {}) {
@@ -32,6 +27,8 @@ async function makeApp(mediaOverride: Partial<TAppMediaConfig> = {}) {
}, },
}); });
app.module.media.adapters.set("local", StorageLocalAdapter);
await app.build(); await app.build();
return app; return app;
} }
+1 -1
View File
@@ -1,5 +1,5 @@
import { beforeEach, describe, expect, test } from "bun:test"; import { beforeEach, describe, expect, test } from "bun:test";
import { parse } from "bknd/core"; import { parse } from "core/object/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";
+2 -8
View File
@@ -1,22 +1,15 @@
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 "../../src/data";
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", () => {
test.only("...", () => {
const media = new AppMedia();
console.log(media.toJSON());
});
moduleTestSuite(AppMedia); moduleTestSuite(AppMedia);
test("should allow additional fields", async () => { test("should allow additional fields", async () => {
registries.media.register("local", StorageLocalAdapter); //registries.media.register("local", StorageLocalAdapter);
const app = createApp({ const app = createApp({
initialConfig: { initialConfig: {
@@ -37,6 +30,7 @@ describe("AppMedia", () => {
}).toJSON(), }).toJSON(),
}, },
}); });
app.module.media.adapters.set("local", StorageLocalAdapter);
await app.build(); await app.build();
+1 -1
View File
@@ -1,5 +1,5 @@
import { describe, expect, test } from "bun:test"; import { describe, expect, test } from "bun:test";
import { s, stripMark } from "bknd/core"; import { s, stripMark } from "core/object/schema";
import { EntityManager, em, entity, index, text } from "../../src/data"; import { EntityManager, em, entity, index, text } from "../../src/data";
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";
+1 -1
View File
@@ -5,7 +5,7 @@ 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 "bknd/core"; import { s, stripMark } from "core/object/schema";
describe("ModuleManager", async () => { describe("ModuleManager", async () => {
test("s1: no config, no build", async () => { test("s1: no config, no build", async () => {
+7
View File
@@ -30,6 +30,7 @@ export type AppPluginConfig = {
onServerInit?: (server: Hono<ServerEnv>) => MaybePromise<void>; onServerInit?: (server: Hono<ServerEnv>) => MaybePromise<void>;
onFirstBoot?: () => MaybePromise<void>; onFirstBoot?: () => MaybePromise<void>;
onBoot?: () => MaybePromise<void>; onBoot?: () => MaybePromise<void>;
onModulesCreated?: (modules: Modules) => void;
}; };
export type AppPlugin = (app: App) => AppPluginConfig; export type AppPlugin = (app: App) => AppPluginConfig;
@@ -113,6 +114,7 @@ export class App<C extends Connection = Connection, Options extends AppOptions =
onFirstBoot: this.onFirstBoot.bind(this), onFirstBoot: this.onFirstBoot.bind(this),
onServerInit: this.onServerInit.bind(this), onServerInit: this.onServerInit.bind(this),
onModulesBuilt: this.onModulesBuilt.bind(this), onModulesBuilt: this.onModulesBuilt.bind(this),
onModulesCreated: this.onModulesCreated.bind(this),
}); });
this.modules.ctx().emgr.registerEvents(AppEvents); this.modules.ctx().emgr.registerEvents(AppEvents);
} }
@@ -325,6 +327,11 @@ export class App<C extends Connection = Connection, Options extends AppOptions =
} }
} }
} }
protected async onModulesCreated(modules: Modules, ctx: ModuleBuildContext) {
await this.runPlugins("onModulesCreated", modules, ctx);
this.options?.manager?.onModulesCreated?.(modules, ctx);
}
} }
export function createApp(config: CreateAppConfig = {}) { export function createApp(config: CreateAppConfig = {}) {
+3 -2
View File
@@ -17,9 +17,8 @@ export async function createApp<Env = BunEnv>(
opts?: RuntimeOptions, opts?: RuntimeOptions,
) { ) {
const root = path.resolve(distPath ?? "./node_modules/bknd/dist", "static"); const root = path.resolve(distPath ?? "./node_modules/bknd/dist", "static");
registerLocalMediaAdapter();
return await createRuntimeApp( const app = await createRuntimeApp(
{ {
...config, ...config,
serveStatic: serveStatic({ root }), serveStatic: serveStatic({ root }),
@@ -27,6 +26,8 @@ export async function createApp<Env = BunEnv>(
args ?? (process.env as Env), args ?? (process.env as Env),
opts, opts,
); );
registerLocalMediaAdapter(app);
return app;
} }
export function createHandler<Env = BunEnv>( export function createHandler<Env = BunEnv>(
+1 -4
View File
@@ -12,10 +12,7 @@ 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 ( if (env[key] && (env[key] as any).constructor.name === type) {
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],
@@ -8,6 +8,7 @@ import { getCached } from "./modes/cached";
import { getDurable } from "./modes/durable"; import { getDurable } from "./modes/durable";
import type { App } from "bknd"; import type { App } from "bknd";
import { $console } from "core/utils"; import { $console } from "core/utils";
import { registerMedia } from "./storage/StorageR2Adapter";
declare global { declare global {
namespace Cloudflare { namespace Cloudflare {
@@ -33,7 +34,7 @@ export type CloudflareBkndConfig<Env = CloudflareEnv> = RuntimeBkndConfig<Env> &
keepAliveSeconds?: number; keepAliveSeconds?: number;
forceHttps?: boolean; forceHttps?: boolean;
manifest?: string; manifest?: string;
registerMedia?: boolean | ((env: Env) => void); registerMedia?: boolean;
}; };
export type Context<Env = CloudflareEnv> = { export type Context<Env = CloudflareEnv> = {
@@ -99,7 +100,16 @@ export function serve<Env extends CloudflareEnv = CloudflareEnv>(
throw new Error(`Unknown mode ${mode}`); throw new Error(`Unknown mode ${mode}`);
} }
registerMediaInternal(app, config, context);
return app.fetch(request, env, ctx); return app.fetch(request, env, ctx);
}, },
}; };
} }
let media_registered: boolean = false;
function registerMediaInternal(app: App, config: CloudflareBkndConfig<any>, ctx?: Context) {
if (!media_registered && config.registerMedia !== false) {
registerMedia(app, ctx?.env as any);
media_registered = true;
}
}
-11
View File
@@ -1,6 +1,5 @@
/// <reference types="@cloudflare/workers-types" /> /// <reference types="@cloudflare/workers-types" />
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 { Connection } from "bknd/data";
@@ -88,20 +87,10 @@ export function d1SessionHelper(config: CloudflareBkndConfig<any>) {
}; };
} }
let media_registered: boolean = false;
export function makeConfig<Env extends CloudflareEnv = CloudflareEnv>( export function makeConfig<Env extends CloudflareEnv = CloudflareEnv>(
config: CloudflareBkndConfig<Env>, config: CloudflareBkndConfig<Env>,
args?: CfMakeConfigArgs<Env>, args?: CfMakeConfigArgs<Env>,
) { ) {
if (!media_registered && config.registerMedia !== false) {
if (typeof config.registerMedia === "function") {
config.registerMedia(args?.env as any);
} else {
registerMedia(args?.env as any);
}
media_registered = true;
}
const appConfig = makeAdapterConfig(config, args?.env); const appConfig = makeAdapterConfig(config, args?.env);
// if connection instance is given, don't do anything // if connection instance is given, don't do anything
+1 -2
View File
@@ -13,8 +13,7 @@ export {
type BindingMap, type BindingMap,
} from "./bindings"; } from "./bindings";
export { constants } from "./config"; export { constants } from "./config";
export { StorageR2Adapter, registerMedia } from "./storage/StorageR2Adapter"; export { StorageR2Adapter } from "./storage/StorageR2Adapter";
export { registries } from "bknd";
// for compatibility with old code // for compatibility with old code
export function d1<DB extends D1Database | D1DatabaseSession = D1Database>( export function d1<DB extends D1Database | D1DatabaseSession = D1Database>(
@@ -1,8 +1,8 @@
import { registries } from "bknd"; import type { App } from "bknd";
import { isDebug } from "bknd/core"; import { isDebug } from "bknd/core";
import { guessMimeType as guess, StorageAdapter, type FileBody } from "bknd/media"; import { guessMimeType as guess, StorageAdapter, type FileBody } from "bknd/media";
import { getBindings } from "../bindings"; import { getBindings } from "../bindings";
import { s } from "bknd/core"; import { s } from "core/object/schema";
export function makeSchema(bindings: string[] = []) { export function makeSchema(bindings: string[] = []) {
return s.object( return s.object(
@@ -13,10 +13,10 @@ export function makeSchema(bindings: string[] = []) {
); );
} }
export function registerMedia(env: Record<string, any>) { export function registerMedia(app: App, env: Record<string, any>) {
const r2_bindings = getBindings(env, "R2Bucket"); const r2_bindings = getBindings(env, "R2Bucket");
registries.media.register( app.module.media.adapters.set(
"r2", "r2",
class extends StorageR2Adapter { class extends StorageR2Adapter {
constructor(private config: any) { constructor(private config: any) {
+3 -2
View File
@@ -4,13 +4,14 @@ import { $console } from "bknd/utils";
import type { MiddlewareHandler } from "hono"; import type { MiddlewareHandler } from "hono";
import type { AdminControllerOptions } from "modules/server/AdminController"; import type { AdminControllerOptions } from "modules/server/AdminController";
import { Connection } from "bknd/data"; import { Connection } from "bknd/data";
import type { MaybePromise } from "core/types";
export { Connection } from "bknd/data"; 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);
onBuilt?: (app: App) => Promise<void>; onBuilt?: (app: App) => MaybePromise<void>;
beforeBuild?: (app: App) => Promise<void>; beforeBuild?: (app: App) => MaybePromise<void>;
buildConfig?: Parameters<App["build"]>[0]; buildConfig?: Parameters<App["build"]>[0];
}; };
+3 -2
View File
@@ -29,8 +29,7 @@ export async function createApp<Env = NodeEnv>(
console.warn("relativeDistPath is deprecated, please use distPath instead"); console.warn("relativeDistPath is deprecated, please use distPath instead");
} }
registerLocalMediaAdapter(); const app = await createRuntimeApp(
return await createRuntimeApp(
{ {
...config, ...config,
serveStatic: serveStatic({ root }), serveStatic: serveStatic({ root }),
@@ -39,6 +38,8 @@ export async function createApp<Env = NodeEnv>(
args ?? { env: process.env }, args ?? { env: process.env },
opts, opts,
); );
registerLocalMediaAdapter(app);
return app;
} }
export function createHandler<Env = NodeEnv>( export function createHandler<Env = NodeEnv>(
@@ -2,7 +2,7 @@ import { readFile, readdir, stat, unlink, writeFile } from "node:fs/promises";
import { isFile } from "bknd/utils"; import { isFile } from "bknd/utils";
import type { FileBody, FileListObject, FileMeta, FileUploadPayload } from "bknd/media"; import type { FileBody, FileListObject, FileMeta, FileUploadPayload } from "bknd/media";
import { StorageAdapter, guessMimeType as guess } from "bknd/media"; import { StorageAdapter, guessMimeType as guess } from "bknd/media";
import { parse, s } from "bknd/core"; import { parse, s } from "core/object/schema";
export const localAdapterConfig = s.object( export const localAdapterConfig = s.object(
{ {
+3 -7
View File
@@ -1,14 +1,10 @@
import { registries } from "bknd"; import type { App } from "bknd";
import { type LocalAdapterConfig, StorageLocalAdapter } from "./StorageLocalAdapter"; import { type LocalAdapterConfig, StorageLocalAdapter } from "./StorageLocalAdapter";
export * from "./StorageLocalAdapter"; export * from "./StorageLocalAdapter";
let registered = false; export function registerLocalMediaAdapter(app: App) {
export function registerLocalMediaAdapter() { app.module.media.adapters.set("local", StorageLocalAdapter);
if (!registered) {
registries.media.register("local", StorageLocalAdapter);
registered = true;
}
return (config: Partial<LocalAdapterConfig> = {}) => { return (config: Partial<LocalAdapterConfig> = {}) => {
const adapter = new StorageLocalAdapter(config); const adapter = new StorageLocalAdapter(config);
+3 -2
View File
@@ -32,8 +32,7 @@ async function createApp<ViteEnv>(
env: ViteEnv = {} as ViteEnv, env: ViteEnv = {} as ViteEnv,
opts: FrameworkOptions = {}, opts: FrameworkOptions = {},
): Promise<App> { ): Promise<App> {
registerLocalMediaAdapter(); const app = await createRuntimeApp(
return await createRuntimeApp(
{ {
...config, ...config,
adminOptions: config.adminOptions ?? { adminOptions: config.adminOptions ?? {
@@ -49,6 +48,8 @@ async function createApp<ViteEnv>(
env, env,
opts, opts,
); );
registerLocalMediaAdapter(app);
return app;
} }
export function serve<ViteEnv>( export function serve<ViteEnv>(
+1 -1
View File
@@ -3,7 +3,7 @@ import { transformObject } from "core/utils";
import { DataPermissions } from "data"; import { DataPermissions } from "data";
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 "bknd/core"; import { describeRoute, jsc, s, parse, InvalidSchemaError } from "core/object/schema";
export type AuthActionResponse = { export type AuthActionResponse = {
success: boolean; success: boolean;
+1 -1
View File
@@ -1,7 +1,7 @@
import { cookieConfig, jwtConfig } from "auth/authenticate/Authenticator"; import { cookieConfig, jwtConfig } from "auth/authenticate/Authenticator";
import { CustomOAuthStrategy, OAuthStrategy, PasswordStrategy } from "auth/authenticate/strategies"; import { CustomOAuthStrategy, OAuthStrategy, PasswordStrategy } from "auth/authenticate/strategies";
import { objectTransform } from "core/utils"; import { objectTransform } from "core/utils";
import { s } from "bknd/core"; import { s } from "core/object/schema";
export const Strategies = { export const Strategies = {
password: { password: {
+1 -1
View File
@@ -8,7 +8,7 @@ import type { CookieOptions } 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 "bknd/core"; import { s, parse, secret } from "core/object/schema";
type Input = any; // workaround type Input = any; // workaround
export type JWTPayload = Parameters<typeof sign>[0]; export type JWTPayload = Parameters<typeof sign>[0];
@@ -3,7 +3,7 @@ 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 { Strategy } from "./Strategy";
import { s, parse, jsc } from "bknd/core"; import { s, parse, jsc } from "core/object/schema";
const schema = s const schema = s
.object({ .object({
@@ -33,7 +33,7 @@ export class PasswordStrategy extends Strategy<typeof schema> {
private getPayloadSchema() { private getPayloadSchema() {
return s.object({ return s.object({
email: s.string({ email: s.string({
format: "email", pattern: /^[\w-\.\+_]+@([\w-]+\.)+[\w-]{2,4}$/,
}), }),
password: s.string({ password: s.string({
minLength: 8, // @todo: this should be configurable minLength: 8, // @todo: this should be configurable
@@ -5,7 +5,7 @@ import type {
StrategyActions, StrategyActions,
} from "../Authenticator"; } from "../Authenticator";
import type { Hono } from "hono"; import type { Hono } from "hono";
import { type s, parse } from "bknd/core"; import { type s, parse } from "core/object/schema";
export type StrategyMode = "form" | "external"; export type StrategyMode = "form" | "external";
@@ -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 "bknd/core"; import { s } from "core/object/schema";
type SupportedTypes = "oauth2" | "oidc"; type SupportedTypes = "oauth2" | "oidc";
@@ -6,7 +6,7 @@ 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 { Strategy } from "auth/authenticate/strategies/Strategy";
import { s } from "bknd/core"; import { s } from "core/object/schema";
type ConfiguredIssuers = keyof typeof issuers; type ConfiguredIssuers = keyof typeof issuers;
type SupportedTypes = "oauth2" | "oidc"; type SupportedTypes = "oauth2" | "oidc";
+4 -9
View File
@@ -1,11 +1,10 @@
import type { Config } from "@libsql/client/node"; import type { Config } from "@libsql/client/node";
import type { App, CreateAppConfig } from "App"; import type { App, CreateAppConfig } from "App";
import { StorageLocalAdapter } from "adapter/node/storage"; import { registerLocalMediaAdapter } 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 } from "core";
import dotenv from "dotenv"; import dotenv from "dotenv";
import { registries } from "modules/registries";
import c from "picocolors"; import c from "picocolors";
import path from "node:path"; import path from "node:path";
import { import {
@@ -57,12 +56,6 @@ export const run: CliCommand = (program) => {
.action(action); .action(action);
}; };
// automatically register local adapter
const local = StorageLocalAdapter.prototype.getName();
if (!registries.media.has(local)) {
registries.media.register(local, StorageLocalAdapter);
}
type MakeAppConfig = { type MakeAppConfig = {
connection?: CreateAppConfig["connection"]; connection?: CreateAppConfig["connection"];
server?: { platform?: Platform }; server?: { platform?: Platform };
@@ -71,10 +64,12 @@ type MakeAppConfig = {
}; };
async function makeApp(config: MakeAppConfig) { async function makeApp(config: MakeAppConfig) {
return await createRuntimeApp({ const app = await createRuntimeApp({
serveStatic: await serveStatic(config.server?.platform ?? "node"), serveStatic: await serveStatic(config.server?.platform ?? "node"),
...config, ...config,
}); });
registerLocalMediaAdapter(app);
return app;
} }
export async function makeConfigApp(_config: CliBkndConfig, platform?: Platform) { export async function makeConfigApp(_config: CliBkndConfig, platform?: Platform) {
+2 -7
View File
@@ -25,12 +25,8 @@ export {
} from "./object/query/query"; } from "./object/query/query";
export { Registry, type Constructor } from "./registry/Registry"; export { Registry, type Constructor } from "./registry/Registry";
export { getFlashMessage } from "./server/flash"; export { getFlashMessage } from "./server/flash";
export { /* export {
s, s,
stripMark,
mark,
stringIdentifier,
secret,
parse, parse,
jsc, jsc,
describeRoute, describeRoute,
@@ -38,8 +34,7 @@ export {
openAPISpecs, openAPISpecs,
type ParseOptions, type ParseOptions,
InvalidSchemaError, InvalidSchemaError,
} from "./object/schema"; } from "./object/schema"; */
export * as $diff from "./object/diff";
export * from "./drivers"; export * from "./drivers";
export * from "./events"; export * from "./events";
+2 -2
View File
@@ -1,6 +1,6 @@
import { get, has, omit, set } from "lodash-es"; import { get, has, omit, set } from "lodash-es";
import { getFullPathKeys, mergeObjectWith } from "../utils"; import { getFullPathKeys, mergeObjectWith } from "../utils";
import { type s, parse, stripMark } from "bknd/core"; 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>;
@@ -29,7 +29,7 @@ export class SchemaObject<Schema extends TSchema = TSchema> {
this._default = _schema.template({}, { withOptional: true }) as any; this._default = _schema.template({}, { withOptional: true }) as any;
this._value = parse(_schema, structuredClone(initial ?? {}), { this._value = parse(_schema, structuredClone(initial ?? {}), {
withDefaults: true, withDefaults: true,
withExtendedDefaults: true, //withExtendedDefaults: true,
forceParse: this.isForceParse(), forceParse: this.isForceParse(),
skipMark: this.isForceParse(), skipMark: this.isForceParse(),
}); });
+1 -1
View File
@@ -8,7 +8,7 @@ import {
import type { Handler } from "hono/types"; import type { Handler } from "hono/types";
import type { ModuleBuildContext } from "modules"; import type { ModuleBuildContext } from "modules";
import { Controller } from "modules/Controller"; import { Controller } from "modules/Controller";
import { jsc, s, describeRoute, schemaToSpec } from "bknd/core"; import { jsc, s, describeRoute, schemaToSpec } from "core/object/schema";
import * as SystemPermissions from "modules/permissions"; import * as SystemPermissions from "modules/permissions";
import type { AppDataConfig } from "../data-schema"; import type { AppDataConfig } from "../data-schema";
import { omitKeys } from "core/utils"; import { omitKeys } from "core/utils";
+4 -6
View File
@@ -4,7 +4,7 @@ import { FieldClassMap } from "data/fields";
import { RelationClassMap, RelationFieldClassMap } from "data/relations"; import { RelationClassMap, RelationFieldClassMap } from "data/relations";
import { entityConfigSchema, entityTypes } from "data/entities"; import { entityConfigSchema, entityTypes } from "data/entities";
import { primaryFieldTypes } from "./fields"; import { primaryFieldTypes } from "./fields";
import { s } from "bknd/core"; import { s } from "core/object/schema";
export const FIELDS = { export const FIELDS = {
...FieldClassMap, ...FieldClassMap,
@@ -18,7 +18,6 @@ export const RELATIONS = RelationClassMap;
export const fieldsSchemaObject = objectTransform(FIELDS, (field, name) => { export const fieldsSchemaObject = objectTransform(FIELDS, (field, name) => {
return s.strictObject( return s.strictObject(
{ {
name: s.string().optional(), // @todo: verify, old schema wasn't strict (req in UI)
type: s.literal(name), type: s.literal(name),
config: field.schema.optional(), config: field.schema.optional(),
}, },
@@ -33,10 +32,9 @@ export type TAppDataField = s.Static<typeof fieldsSchema>;
export type TAppDataEntityFields = s.Static<typeof entityFields>; export type TAppDataEntityFields = s.Static<typeof entityFields>;
export const entitiesSchema = s.strictObject({ export const entitiesSchema = s.strictObject({
name: s.string().optional(), // @todo: verify, old schema wasn't strict (req in UI) type: s.string({ enum: entityTypes, default: "regular", readOnly: true }),
type: s.string({ enum: entityTypes, default: "regular" }), config: entityConfigSchema.optional(),
config: entityConfigSchema, fields: entityFields.optional(),
fields: entityFields,
}); });
export type TAppDataEntity = s.Static<typeof entitiesSchema>; export type TAppDataEntity = s.Static<typeof entitiesSchema>;
+1 -1
View File
@@ -7,7 +7,7 @@ import {
type TActionContext, type TActionContext,
type TRenderContext, type TRenderContext,
} from "../fields"; } from "../fields";
import { s, parse } from "bknd/core"; import { s, parse } from "core/object/schema";
// @todo: entity must be migrated to typebox // @todo: entity must be migrated to typebox
export const entityConfigSchema = s export const entityConfigSchema = s
+1 -1
View File
@@ -1,6 +1,6 @@
import { Exception } from "core"; import { Exception } from "core";
import { HttpStatus } from "core/utils"; import { HttpStatus } from "core/utils";
import type { InvalidSchemaError } from "bknd/core"; import type { InvalidSchemaError } from "core/object/schema";
import type { Entity } from "./entities"; import type { Entity } from "./entities";
import type { Field } from "./fields"; import type { Field } from "./fields";
+1 -1
View File
@@ -2,7 +2,7 @@ import { omitKeys } from "core/utils";
import type { EntityManager } from "data"; import type { EntityManager } from "data";
import { TransformPersistFailedException } from "../errors"; import { TransformPersistFailedException } from "../errors";
import { Field, type TActionContext, type TRenderContext, baseFieldConfigSchema } from "./Field"; import { Field, type TActionContext, type TRenderContext, baseFieldConfigSchema } from "./Field";
import { s } from "bknd/core"; import { s } from "core/object/schema";
export const booleanFieldConfigSchema = s export const booleanFieldConfigSchema = s
.strictObject({ .strictObject({
+1 -1
View File
@@ -3,7 +3,7 @@ import type { EntityManager } from "../entities";
import { Field, type TActionContext, type TRenderContext, baseFieldConfigSchema } from "./Field"; import { Field, type TActionContext, type TRenderContext, baseFieldConfigSchema } from "./Field";
import { $console } from "core/utils"; import { $console } from "core/utils";
import type { TFieldTSType } from "data/entities/EntityTypescript"; import type { TFieldTSType } from "data/entities/EntityTypescript";
import { s } from "bknd/core"; import { s } from "core/object/schema";
export const dateFieldConfigSchema = s export const dateFieldConfigSchema = s
.strictObject({ .strictObject({
+1 -1
View File
@@ -3,7 +3,7 @@ import type { EntityManager } from "data";
import { TransformPersistFailedException } from "../errors"; import { TransformPersistFailedException } from "../errors";
import { baseFieldConfigSchema, Field, type TActionContext, type TRenderContext } from "./Field"; import { baseFieldConfigSchema, Field, type TActionContext, type TRenderContext } from "./Field";
import type { TFieldTSType } from "data/entities/EntityTypescript"; import type { TFieldTSType } from "data/entities/EntityTypescript";
import { s } from "bknd/core"; import { s } from "core/object/schema";
export const enumFieldConfigSchema = s export const enumFieldConfigSchema = s
.strictObject({ .strictObject({
+1 -1
View File
@@ -4,7 +4,7 @@ import type { EntityManager } from "../entities";
import { InvalidFieldConfigException, TransformPersistFailedException } from "../errors"; import { InvalidFieldConfigException, TransformPersistFailedException } from "../errors";
import type { FieldSpec } from "data/connection/Connection"; import type { FieldSpec } from "data/connection/Connection";
import type { TFieldTSType } from "data/entities/EntityTypescript"; import type { TFieldTSType } from "data/entities/EntityTypescript";
import { s, parse, InvalidSchemaError } from "bknd/core"; import { s, parse, InvalidSchemaError } from "core/object/schema";
// @todo: contexts need to be reworked // @todo: contexts need to be reworked
// e.g. "table" is irrelevant, because if read is not given, it fails // e.g. "table" is irrelevant, because if read is not given, it fails
+1 -1
View File
@@ -3,7 +3,7 @@ import type { EntityManager } from "data";
import { TransformPersistFailedException } from "../errors"; import { TransformPersistFailedException } from "../errors";
import { Field, type TActionContext, type TRenderContext, baseFieldConfigSchema } from "./Field"; import { Field, type TActionContext, type TRenderContext, baseFieldConfigSchema } from "./Field";
import type { TFieldTSType } from "data/entities/EntityTypescript"; import type { TFieldTSType } from "data/entities/EntityTypescript";
import { s } from "bknd/core"; import { s } from "core/object/schema";
export const jsonFieldConfigSchema = s export const jsonFieldConfigSchema = s
.strictObject({ .strictObject({
+1 -1
View File
@@ -4,7 +4,7 @@ import type { EntityManager } from "data";
import { TransformPersistFailedException } from "../errors"; import { TransformPersistFailedException } from "../errors";
import { Field, type TActionContext, type TRenderContext, baseFieldConfigSchema } from "./Field"; import { Field, type TActionContext, type TRenderContext, baseFieldConfigSchema } from "./Field";
import type { TFieldTSType } from "data/entities/EntityTypescript"; import type { TFieldTSType } from "data/entities/EntityTypescript";
import { s } from "bknd/core"; import { s } from "core/object/schema";
export const jsonSchemaFieldConfigSchema = s export const jsonSchemaFieldConfigSchema = s
.strictObject({ .strictObject({
+1 -1
View File
@@ -2,7 +2,7 @@ import type { EntityManager } from "data";
import { TransformPersistFailedException } from "../errors"; import { TransformPersistFailedException } from "../errors";
import { Field, type TActionContext, type TRenderContext, baseFieldConfigSchema } from "./Field"; import { Field, type TActionContext, type TRenderContext, baseFieldConfigSchema } from "./Field";
import type { TFieldTSType } from "data/entities/EntityTypescript"; import type { TFieldTSType } from "data/entities/EntityTypescript";
import { s } from "bknd/core"; import { s } from "core/object/schema";
import { omitKeys } from "core/utils"; import { omitKeys } from "core/utils";
export const numberFieldConfigSchema = s export const numberFieldConfigSchema = s
+1 -1
View File
@@ -2,7 +2,7 @@ import { config } from "core";
import { omitKeys, uuidv7 } from "core/utils"; import { omitKeys, uuidv7 } from "core/utils";
import { Field, baseFieldConfigSchema } from "./Field"; import { Field, baseFieldConfigSchema } from "./Field";
import type { TFieldTSType } from "data/entities/EntityTypescript"; import type { TFieldTSType } from "data/entities/EntityTypescript";
import { s } from "bknd/core"; import { s } from "core/object/schema";
export const primaryFieldTypes = ["integer", "uuid"] as const; export const primaryFieldTypes = ["integer", "uuid"] as const;
export type TPrimaryFieldFormat = (typeof primaryFieldTypes)[number]; export type TPrimaryFieldFormat = (typeof primaryFieldTypes)[number];
+1 -1
View File
@@ -2,7 +2,7 @@ import type { EntityManager } from "data";
import { omitKeys } from "core/utils"; import { omitKeys } from "core/utils";
import { TransformPersistFailedException } from "../errors"; import { TransformPersistFailedException } from "../errors";
import { Field, type TActionContext, baseFieldConfigSchema } from "./Field"; import { Field, type TActionContext, baseFieldConfigSchema } from "./Field";
import { s } from "bknd/core"; import { s } from "core/object/schema";
export const textFieldConfigSchema = s export const textFieldConfigSchema = s
.strictObject({ .strictObject({
+1 -1
View File
@@ -1,5 +1,5 @@
import { Field, baseFieldConfigSchema } from "./Field"; import { Field, baseFieldConfigSchema } from "./Field";
import { s } from "bknd/core"; import { s } from "core/object/schema";
export const virtualFieldConfigSchema = s export const virtualFieldConfigSchema = s
.strictObject({ .strictObject({
+2 -2
View File
@@ -1,4 +1,4 @@
import { parse } from "bknd/core"; import { parse } from "core/object/schema";
import type { ExpressionBuilder, SelectQueryBuilder } from "kysely"; import type { ExpressionBuilder, SelectQueryBuilder } from "kysely";
import type { Entity, EntityData, EntityManager } from "../entities"; import type { Entity, EntityData, EntityManager } from "../entities";
import { import {
@@ -9,7 +9,7 @@ import {
import type { RepoQuery } from "../server/query"; import type { RepoQuery } from "../server/query";
import type { RelationType } from "./relation-types"; import type { RelationType } from "./relation-types";
import type { PrimaryFieldType } from "core"; import type { PrimaryFieldType } from "core";
import { s } from "bknd/core"; import { s } from "core/object/schema";
const directions = ["source", "target"] as const; const directions = ["source", "target"] as const;
export type TDirection = (typeof directions)[number]; export type TDirection = (typeof directions)[number];
+1 -1
View File
@@ -6,7 +6,7 @@ import { EntityRelation, type KyselyQueryBuilder } from "./EntityRelation";
import { EntityRelationAnchor } from "./EntityRelationAnchor"; import { EntityRelationAnchor } from "./EntityRelationAnchor";
import { RelationField } from "./RelationField"; import { RelationField } from "./RelationField";
import { type RelationType, RelationTypes } from "./relation-types"; import { type RelationType, RelationTypes } from "./relation-types";
import { s } from "bknd/core"; import { s } from "core/object/schema";
export type ManyToManyRelationConfig = s.Static<typeof ManyToManyRelation.schema>; export type ManyToManyRelationConfig = s.Static<typeof ManyToManyRelation.schema>;
+1 -1
View File
@@ -8,7 +8,7 @@ import { EntityRelationAnchor } from "./EntityRelationAnchor";
import { RelationField, type RelationFieldBaseConfig } from "./RelationField"; import { RelationField, type RelationFieldBaseConfig } from "./RelationField";
import type { MutationInstructionResponse } from "./RelationMutator"; import type { MutationInstructionResponse } from "./RelationMutator";
import { type RelationType, RelationTypes } from "./relation-types"; import { type RelationType, RelationTypes } from "./relation-types";
import { s } from "bknd/core"; import { s } from "core/object/schema";
/** /**
* Source entity receives the mapping field * Source entity receives the mapping field
@@ -5,7 +5,7 @@ import type { RepoQuery } from "../server/query";
import { EntityRelation, type KyselyJsonFrom, type KyselyQueryBuilder } from "./EntityRelation"; import { EntityRelation, type KyselyJsonFrom, type KyselyQueryBuilder } from "./EntityRelation";
import { EntityRelationAnchor } from "./EntityRelationAnchor"; import { EntityRelationAnchor } from "./EntityRelationAnchor";
import { type RelationType, RelationTypes } from "./relation-types"; import { type RelationType, RelationTypes } from "./relation-types";
import { s } from "bknd/core"; import { s } from "core/object/schema";
export type PolymorphicRelationConfig = s.Static<typeof PolymorphicRelation.schema>; export type PolymorphicRelationConfig = s.Static<typeof PolymorphicRelation.schema>;
+1 -1
View File
@@ -3,7 +3,7 @@ import { Field, baseFieldConfigSchema } from "../fields";
import type { EntityRelation } from "./EntityRelation"; import type { EntityRelation } from "./EntityRelation";
import type { EntityRelationAnchor } from "./EntityRelationAnchor"; import type { EntityRelationAnchor } from "./EntityRelationAnchor";
import type { TFieldTSType } from "data/entities/EntityTypescript"; import type { TFieldTSType } from "data/entities/EntityTypescript";
import { s } from "bknd/core"; import { s } from "core/object/schema";
const CASCADES = ["cascade", "set null", "set default", "restrict", "no action"] as const; const CASCADES = ["cascade", "set null", "set default", "restrict", "no action"] as const;
+1 -1
View File
@@ -1,6 +1,6 @@
import { test, describe, expect } from "bun:test"; import { test, describe, expect } from "bun:test";
import * as q from "./query"; import * as q from "./query";
import { s as schema, parse as $parse, type ParseOptions } from "bknd/core"; import { s as schema, parse as $parse, type ParseOptions } from "core/object/schema";
const parse = (v: unknown, o: ParseOptions = {}) => const parse = (v: unknown, o: ParseOptions = {}) =>
$parse(q.repoQuery, v, { $parse(q.repoQuery, v, {
+1 -1
View File
@@ -1,4 +1,4 @@
import { s } from "bknd/core"; import { s } from "core/object/schema";
import { WhereBuilder, type WhereQuery } from "data/entities/query/WhereBuilder"; import { WhereBuilder, type WhereQuery } from "data/entities/query/WhereBuilder";
import { isObject, $console } from "core/utils"; import { isObject, $console } from "core/utils";
import type { anyOf, CoercionOptions, Schema } from "jsonv-ts"; import type { anyOf, CoercionOptions, Schema } from "jsonv-ts";
+1 -1
View File
@@ -3,7 +3,7 @@ import { Flow, HttpTrigger } from "flows";
import { Hono } from "hono"; import { Hono } from "hono";
import { Module } from "modules/Module"; import { Module } from "modules/Module";
import { TASKS, flowsConfigSchema } from "./flows-schema"; import { TASKS, flowsConfigSchema } from "./flows-schema";
import type { s } from "bknd/core"; import type { s } from "core/object/schema";
export type AppFlowsSchema = s.Static<typeof flowsConfigSchema>; export type AppFlowsSchema = s.Static<typeof flowsConfigSchema>;
export type TAppFlowSchema = AppFlowsSchema["flows"][number]; export type TAppFlowSchema = AppFlowsSchema["flows"][number];
+1 -1
View File
@@ -1,6 +1,6 @@
import { transformObject } from "core/utils"; import { transformObject } from "core/utils";
import { TaskMap, TriggerMap } from "flows"; import { TaskMap, TriggerMap } from "flows";
import { s } from "bknd/core"; import { s } from "core/object/schema";
export const TASKS = { export const TASKS = {
...TaskMap, ...TaskMap,
+1 -1
View File
@@ -2,7 +2,7 @@ import type { EventManager } from "core/events";
import type { Flow } from "../Flow"; import type { Flow } from "../Flow";
import { Trigger } from "./Trigger"; import { Trigger } from "./Trigger";
import { $console } from "core/utils"; import { $console } from "core/utils";
import { s } from "bknd/core"; import { s } from "core/object/schema";
export class EventTrigger extends Trigger<typeof EventTrigger.schema> { export class EventTrigger extends Trigger<typeof EventTrigger.schema> {
override type = "event"; override type = "event";
+1 -1
View File
@@ -1,7 +1,7 @@
import type { Context, Hono } from "hono"; import type { Context, Hono } from "hono";
import type { Flow } from "../Flow"; import type { Flow } from "../Flow";
import { Trigger } from "./Trigger"; import { Trigger } from "./Trigger";
import { s } from "bknd/core"; import { s } from "core/object/schema";
const httpMethods = ["GET", "POST", "PUT", "PATCH", "DELETE"] as const; const httpMethods = ["GET", "POST", "PUT", "PATCH", "DELETE"] as const;
+1 -1
View File
@@ -1,6 +1,6 @@
import type { Execution } from "../Execution"; import type { Execution } from "../Execution";
import type { Flow } from "../Flow"; import type { Flow } from "../Flow";
import { s, parse } from "bknd/core"; import { s, parse } from "core/object/schema";
export class Trigger<Schema extends typeof Trigger.schema = typeof Trigger.schema> { export class Trigger<Schema extends typeof Trigger.schema = typeof Trigger.schema> {
// @todo: remove this // @todo: remove this
+1 -1
View File
@@ -1,7 +1,7 @@
//import { BkndError, SimpleRenderer } from "core"; //import { BkndError, SimpleRenderer } from "core";
import { BkndError } from "core/errors"; import { BkndError } from "core/errors";
import { s, parse } from "bknd/core"; import { s, parse } from "core/object/schema";
import type { InputsMap } from "../flows/Execution"; import type { InputsMap } from "../flows/Execution";
import { SimpleRenderer } from "core/template/SimpleRenderer"; import { SimpleRenderer } from "core/template/SimpleRenderer";
+1 -1
View File
@@ -1,5 +1,5 @@
import { Task, dynamic } from "../Task"; import { Task, dynamic } from "../Task";
import { s } from "bknd/core"; import { s } from "core/object/schema";
const FetchMethods = ["GET", "POST", "PUT", "PATCH", "DELETE"]; const FetchMethods = ["GET", "POST", "PUT", "PATCH", "DELETE"];
+1 -1
View File
@@ -1,6 +1,6 @@
import { Task } from "../Task"; import { Task } from "../Task";
import { $console } from "core/utils"; import { $console } from "core/utils";
import { s } from "bknd/core"; import { s } from "core/object/schema";
export class LogTask extends Task<typeof LogTask.schema> { export class LogTask extends Task<typeof LogTask.schema> {
type = "log"; type = "log";
+1 -1
View File
@@ -1,5 +1,5 @@
import { Task } from "../Task"; import { Task } from "../Task";
import { s } from "bknd/core"; import { s } from "core/object/schema";
export class RenderTask<Output extends Record<string, any>> extends Task< export class RenderTask<Output extends Record<string, any>> extends Task<
typeof RenderTask.schema, typeof RenderTask.schema,
+1 -1
View File
@@ -1,6 +1,6 @@
import { Flow } from "../../flows/Flow"; import { Flow } from "../../flows/Flow";
import { Task, dynamic } from "../Task"; import { Task, dynamic } from "../Task";
import { s } from "bknd/core"; import { s } from "core/object/schema";
export class SubFlowTask<Output extends Record<string, any>> extends Task< export class SubFlowTask<Output extends Record<string, any>> extends Task<
typeof SubFlowTask.schema, typeof SubFlowTask.schema,
-1
View File
@@ -34,7 +34,6 @@ export type { ServerEnv } from "modules/Controller";
export type { BkndConfig } from "bknd/adapter"; export type { BkndConfig } from "bknd/adapter";
export * as middlewares from "modules/middlewares"; export * as middlewares from "modules/middlewares";
export { registries } from "modules/registries";
export type { MediaFieldSchema } from "media/AppMedia"; export type { MediaFieldSchema } from "media/AppMedia";
export type { UserFieldSchema } from "auth/AppAuth"; export type { UserFieldSchema } from "auth/AppAuth";
+48 -6
View File
@@ -1,12 +1,15 @@
import type { AppEntity } from "core"; import type { AppEntity, Constructor } from "core";
import { $console } from "core/utils"; import { $console, objectTransform } from "core/utils";
import type { Entity, EntityManager } from "data"; import type { Entity, EntityManager } from "data";
import { type FileUploadedEventData, Storage, type StorageAdapter, MediaPermissions } from "media"; import { type FileUploadedEventData, Storage, type StorageAdapter, MediaPermissions } from "media";
import { Module } from "modules/Module"; import { Module, type ModuleBuildContext } from "modules/Module";
import { type FieldSchema, em, entity } from "../data/prototype"; import { type FieldSchema, em, entity } from "../data/prototype";
import { MediaController } from "./api/MediaController"; import { MediaController } from "./api/MediaController";
import { buildMediaSchema, type mediaConfigSchema, registry, type TAppMediaConfig } from "./media-schema"; import { mediaConfigSchema, type TAppMediaConfig } from "./media-schema";
import { mediaFields } from "./media-entities"; import { mediaFields } from "./media-entities";
import { StorageS3Adapter } from "media/storage/adapters/s3/StorageS3Adapter";
import { StorageCloudinaryAdapter } from "media/storage/adapters/cloudinary/StorageCloudinaryAdapter";
import { s } from "core/object/schema";
export type MediaFieldSchema = FieldSchema<typeof AppMedia.mediaFields>; export type MediaFieldSchema = FieldSchema<typeof AppMedia.mediaFields>;
declare module "core" { declare module "core" {
@@ -15,10 +18,18 @@ declare module "core" {
media: Media; media: Media;
} }
} }
type ClassThatImplements<T> = Constructor<T> & { prototype: T };
// @todo: current workaround to make it all required // @todo: current workaround to make it all required
export class AppMedia extends Module<Required<TAppMediaConfig>> { export class AppMedia extends Module<Required<TAppMediaConfig>> {
private _storage?: Storage; private _storage?: Storage;
adapters: Map<string, ClassThatImplements<StorageAdapter>> = new Map();
constructor(initial?: Partial<TAppMediaConfig>, _ctx?: ModuleBuildContext) {
super(initial, _ctx);
this.adapters.set("s3", StorageS3Adapter);
this.adapters.set("cloudinary", StorageCloudinaryAdapter);
}
override async build() { override async build() {
if (!this.config.enabled) { if (!this.config.enabled) {
@@ -35,7 +46,11 @@ export class AppMedia extends Module<Required<TAppMediaConfig>> {
let adapter: StorageAdapter; let adapter: StorageAdapter;
try { try {
const { type, config } = this.config.adapter; const { type, config } = this.config.adapter;
const cls = registry.get(type as any).cls; const cls = this.adapters.get(type as any);
if (!cls) {
throw new Error(`Adapter ${type} not found`);
}
adapter = new cls(config as any); adapter = new cls(config as any);
this._storage = new Storage(adapter, this.config.storage, this.ctx.emgr); this._storage = new Storage(adapter, this.config.storage, this.ctx.emgr);
@@ -59,7 +74,34 @@ export class AppMedia extends Module<Required<TAppMediaConfig>> {
} }
getSchema() { getSchema() {
return buildMediaSchema(); if (!this.adapters) {
return mediaConfigSchema;
}
const adapterSchemaObject = objectTransform(
Object.fromEntries(this.adapters.entries()),
(adapter, name) => {
const schema = adapter.prototype.getSchema();
if (!schema) {
throw new Error(`Adapter ${name} has no schema`);
}
return s.strictObject(
{
type: s.literal(name),
config: schema,
},
{
title: String(schema.title ?? name),
description: schema.description,
},
);
},
);
return s.strictObject({
...mediaConfigSchema.properties,
adapter: s.anyOf(Object.values(adapterSchemaObject)).optional(),
});
} }
get basepath() { get basepath() {
+1 -1
View File
@@ -1,5 +1,5 @@
import { Field, baseFieldConfigSchema } from "data/fields"; import { Field, baseFieldConfigSchema } from "data/fields";
import { s } from "bknd/core"; import { s } from "core/object/schema";
export const mediaFieldConfigSchema = s export const mediaFieldConfigSchema = s
.strictObject({ .strictObject({
+1 -1
View File
@@ -6,7 +6,7 @@ import { DataPermissions } from "data";
import { Controller } from "modules/Controller"; import { Controller } from "modules/Controller";
import type { AppMedia } from "../AppMedia"; import type { AppMedia } from "../AppMedia";
import { MediaField } from "../MediaField"; import { MediaField } from "../MediaField";
import { jsc, s, describeRoute } from "bknd/core"; import { jsc, s, describeRoute } from "core/object/schema";
export class MediaController extends Controller { export class MediaController extends Controller {
constructor(private readonly media: AppMedia) { constructor(private readonly media: AppMedia) {
-26
View File
@@ -1,5 +1,3 @@
import { type Constructor, Registry } from "core";
export { guess as guessMimeType } from "./storage/mime-types-tiny"; export { guess as guessMimeType } from "./storage/mime-types-tiny";
export { export {
Storage, Storage,
@@ -15,7 +13,6 @@ import {
StorageCloudinaryAdapter, StorageCloudinaryAdapter,
} from "./storage/adapters/cloudinary/StorageCloudinaryAdapter"; } from "./storage/adapters/cloudinary/StorageCloudinaryAdapter";
import { type S3AdapterConfig, StorageS3Adapter } from "./storage/adapters/s3/StorageS3Adapter"; import { type S3AdapterConfig, StorageS3Adapter } from "./storage/adapters/s3/StorageS3Adapter";
import type { s } from "bknd/core";
export { StorageAdapter }; export { StorageAdapter };
export { StorageS3Adapter, type S3AdapterConfig, StorageCloudinaryAdapter, type CloudinaryConfig }; export { StorageS3Adapter, type S3AdapterConfig, StorageCloudinaryAdapter, type CloudinaryConfig };
@@ -25,27 +22,4 @@ export * as MediaPermissions from "./media-permissions";
export type { FileUploadedEventData } from "./storage/events"; export type { FileUploadedEventData } from "./storage/events";
export * from "./utils"; export * from "./utils";
type ClassThatImplements<T> = Constructor<T> & { prototype: T };
export const MediaAdapterRegistry = new Registry<{
cls: ClassThatImplements<StorageAdapter>;
schema: s.Schema;
}>((cls: ClassThatImplements<StorageAdapter>) => ({
cls,
schema: cls.prototype.getSchema() as s.Schema,
}))
.register("s3", StorageS3Adapter)
.register("cloudinary", StorageCloudinaryAdapter);
export const Adapters = {
s3: {
cls: StorageS3Adapter,
schema: StorageS3Adapter.prototype.getSchema(),
},
cloudinary: {
cls: StorageCloudinaryAdapter,
schema: StorageCloudinaryAdapter.prototype.getSchema(),
},
} as const;
export { adapterTestSuite } from "./storage/adapters/adapter-test-suite"; export { adapterTestSuite } from "./storage/adapters/adapter-test-suite";
+25 -46
View File
@@ -1,50 +1,29 @@
import { objectTransform } from "core/utils"; import { s } from "core/object/schema";
import { Adapters } from "media";
import { registries } from "modules/registries";
import { s } from "bknd/core";
export const ADAPTERS = { export const mediaConfigSchema = s.strictObject(
...Adapters, {
} as const; enabled: s.boolean({ default: false }),
basepath: s.string({ default: "/api/media" }),
export const registry = registries.media; entity_name: s.string({ default: "media" }),
storage: s.strictObject(
export function buildMediaSchema() {
const adapterSchemaObject = objectTransform(registry.all(), (adapter, name) => {
return s.strictObject(
{ {
type: s.literal(name), body_max_size: s
config: adapter.schema, .number({
description: "Max size of the body in bytes. Leave blank for unlimited.",
})
.optional(),
}, },
{ { default: {} },
title: adapter.schema?.title ?? name, ),
description: adapter.schema?.description, adapter: s
}, .strictObject({
); type: s.string(),
}); config: s.any(),
})
return s.strictObject( .optional(),
{ },
enabled: s.boolean({ default: false }), {
basepath: s.string({ default: "/api/media" }), default: {},
entity_name: s.string({ default: "media" }), },
storage: s.strictObject( );
{
body_max_size: s
.number({
description: "Max size of the body in bytes. Leave blank for unlimited.",
})
.optional(),
},
{ default: {} },
),
adapter: s.anyOf(Object.values(adapterSchemaObject)).optional(),
},
{
default: {},
},
);
}
export const mediaConfigSchema = buildMediaSchema();
export type TAppMediaConfig = s.Static<typeof mediaConfigSchema>; export type TAppMediaConfig = s.Static<typeof mediaConfigSchema>;
+1 -1
View File
@@ -1,6 +1,6 @@
import type { FileListObject, FileMeta } from "media"; import type { FileListObject, FileMeta } from "media";
import type { FileBody, FileUploadPayload } from "media/storage/Storage"; import type { FileBody, FileUploadPayload } from "media/storage/Storage";
import type { s } from "bknd/core"; import type { s } from "core/object/schema";
const SYMBOL = Symbol.for("bknd:storage"); const SYMBOL = Symbol.for("bknd:storage");
@@ -1,7 +1,7 @@
import { hash, pickHeaders } from "core/utils"; import { hash, pickHeaders } from "core/utils";
import type { FileBody, FileListObject, FileMeta } from "../../Storage"; import type { FileBody, FileListObject, FileMeta } from "../../Storage";
import { StorageAdapter } from "../../StorageAdapter"; import { StorageAdapter } from "../../StorageAdapter";
import { s, parse } from "bknd/core"; import { s, parse } from "core/object/schema";
export const cloudinaryAdapterConfig = s.object( export const cloudinaryAdapterConfig = s.object(
{ {
@@ -11,7 +11,7 @@ import { isFile, pickHeaders2 } from "core/utils";
import { transform } from "lodash-es"; import { transform } from "lodash-es";
import type { FileBody, FileListObject } from "../../Storage"; import type { FileBody, FileListObject } from "../../Storage";
import { StorageAdapter } from "../../StorageAdapter"; import { StorageAdapter } from "../../StorageAdapter";
import { parse, s } from "bknd/core"; import { parse, s } from "core/object/schema";
export const s3AdapterConfig = s.object( export const s3AdapterConfig = s.object(
{ {
+1 -1
View File
@@ -3,7 +3,7 @@ import { type Context, type Env, Hono } from "hono";
import * as middlewares from "modules/middlewares"; import * as middlewares from "modules/middlewares";
import type { SafeUser } from "auth"; import type { SafeUser } from "auth";
import type { EntityManager } from "data"; import type { EntityManager } from "data";
import { s } from "bknd/core"; import { s } from "core/object/schema";
export interface ServerEnv extends Env { export interface ServerEnv extends Env {
Variables: { Variables: {
+4 -1
View File
@@ -5,7 +5,7 @@ import type { Connection, EntityManager } from "data";
import type { Hono } from "hono"; import type { Hono } from "hono";
import type { ServerEnv } from "modules/Controller"; import type { ServerEnv } from "modules/Controller";
import type { ModuleHelper } from "./ModuleHelper"; import type { ModuleHelper } from "./ModuleHelper";
import type { s } from "bknd/core"; import type { s } from "core/object/schema";
type PartialRec<T> = { [P in keyof T]?: PartialRec<T[P]> }; type PartialRec<T> = { [P in keyof T]?: PartialRec<T[P]> };
@@ -38,8 +38,11 @@ export abstract class Module<Schema extends object = object> {
overwritePaths: this.getOverwritePaths(), overwritePaths: this.getOverwritePaths(),
onBeforeUpdate: this.onBeforeUpdate.bind(this), onBeforeUpdate: this.onBeforeUpdate.bind(this),
}); });
this.init();
} }
init() {}
static ctx_flags = { static ctx_flags = {
sync_required: false, sync_required: false,
ctx_reload_required: false, ctx_reload_required: false,
+18 -18
View File
@@ -20,8 +20,7 @@ import { AppMedia } from "../media/AppMedia";
import type { ServerEnv } from "./Controller"; import type { ServerEnv } from "./Controller";
import { Module, type ModuleBuildContext } from "./Module"; import { Module, type ModuleBuildContext } from "./Module";
import { ModuleHelper } from "./ModuleHelper"; import { ModuleHelper } from "./ModuleHelper";
import { s } from "bknd/core"; import { s } from "core/object/schema";
import { mark, stripMark } from "bknd/core";
export type { ModuleBuildContext }; export type { ModuleBuildContext };
@@ -81,6 +80,8 @@ export type ModuleManagerOptions = {
seed?: (ctx: ModuleBuildContext) => Promise<void>; seed?: (ctx: ModuleBuildContext) => Promise<void>;
// called right after modules are built, before finish // called right after modules are built, before finish
onModulesBuilt?: (ctx: ModuleBuildContext) => Promise<void>; onModulesBuilt?: (ctx: ModuleBuildContext) => Promise<void>;
// called right after modules are created, before build
onModulesCreated?: (modules: Modules, ctx: ModuleBuildContext) => void;
/** @deprecated */ /** @deprecated */
verbosity?: Verbosity; verbosity?: Verbosity;
}; };
@@ -128,16 +129,16 @@ export class ModuleManagerConfigUpdateEvent<
}> { }> {
static override slug = "mm-config-update"; static override slug = "mm-config-update";
} }
export class ModuleManagerModulesCreatedEvent extends ModuleManagerEvent<{
export class ModuleManagerConfigDiffEvent extends ModuleManagerEvent<{ modules: Modules;
diffs: $diff.DiffEntry[]; ctx: ModuleBuildContext;
}> { }> {
static override slug = "mm-config-diff"; static override slug = "mm-modules-created";
} }
export const ModuleManagerEvents = { export const ModuleManagerEvents = {
ModuleManagerConfigUpdateEvent, ModuleManagerConfigUpdateEvent,
ModuleManagerConfigDiffEvent, ModuleManagerModulesCreatedEvent,
}; };
// @todo: cleanup old diffs on upgrade // @todo: cleanup old diffs on upgrade
@@ -175,7 +176,8 @@ export class ModuleManager {
if ("version" in options.initial) { if ("version" in options.initial) {
const { version, ...initialConfig } = options.initial; const { version, ...initialConfig } = options.initial;
this._version = version; this._version = version;
initial = stripMark(initialConfig); //initial = stripMark(initialConfig);
initial = initialConfig;
this._booted_with = "provided"; this._booted_with = "provided";
} else { } else {
@@ -203,6 +205,11 @@ export class ModuleManager {
this.modules[key] = module; this.modules[key] = module;
} }
if (this.options?.onModulesCreated) {
this.options.onModulesCreated(this.modules, context);
}
this.logger.log("modules created"); this.logger.log("modules created");
} catch (e) { } catch (e) {
this.logger.log("failed to create modules", e); this.logger.log("failed to create modules", e);
@@ -357,13 +364,6 @@ export class ModuleManager {
// validate diffs, it'll throw on invalid // validate diffs, it'll throw on invalid
this.validateDiffs(diffs); this.validateDiffs(diffs);
await this.emgr.emit(
new ModuleManagerConfigDiffEvent({
ctx: this.ctx(),
diffs: $diff.clone(diffs),
}),
);
const date = new Date(); const date = new Date();
// store diff // store diff
await this.mutator().insertOne({ await this.mutator().insertOne({
@@ -503,10 +503,11 @@ export class ModuleManager {
// set version and config from fetched // set version and config from fetched
this._version = result.version; this._version = result.version;
if (this.options?.trustFetched === true) { // @todo: add back?
/* if (this.options?.trustFetched === true) {
this.logger.log("trusting fetched config (mark)"); this.logger.log("trusting fetched config (mark)");
mark(result.json); mark(result.json);
} } */
// if version doesn't match, migrate before building // if version doesn't match, migrate before building
if (this.version() !== CURRENT_VERSION) { if (this.version() !== CURRENT_VERSION) {
@@ -748,7 +749,6 @@ export function getDefaultSchema() {
export function getDefaultConfig(): ModuleConfigs { export function getDefaultConfig(): ModuleConfigs {
const config = transformObject(MODULES, (module) => { const config = transformObject(MODULES, (module) => {
return module.prototype.getSchema().template(); return module.prototype.getSchema().template();
//return Default(module.prototype.getSchema(), {});
}); });
return config as any; return config as any;
-7
View File
@@ -1,7 +0,0 @@
import { MediaAdapterRegistry } from "media";
const registries = {
media: MediaAdapterRegistry,
} as const;
export { registries };
+1 -1
View File
@@ -3,7 +3,7 @@ import { $console } from "core/utils";
import { cors } from "hono/cors"; import { cors } from "hono/cors";
import { Module } from "modules/Module"; import { Module } from "modules/Module";
import { AuthException } from "auth/errors"; import { AuthException } from "auth/errors";
import { s } from "bknd/core"; import { s } from "core/object/schema";
const serverMethods = ["GET", "POST", "PATCH", "PUT", "DELETE"] as const; const serverMethods = ["GET", "POST", "PATCH", "PUT", "DELETE"] as const;
+1 -1
View File
@@ -14,7 +14,7 @@ import {
type ModuleKey, type ModuleKey,
} from "modules/ModuleManager"; } from "modules/ModuleManager";
import * as SystemPermissions from "modules/permissions"; import * as SystemPermissions from "modules/permissions";
import { jsc, s, describeRoute, InvalidSchemaError } from "bknd/core"; import { jsc, s, describeRoute, InvalidSchemaError } from "core/object/schema";
import { getVersion } from "core/env"; import { getVersion } from "core/env";
import { SecretSchema } from "core/object/schema/secret"; import { SecretSchema } from "core/object/schema/secret";
-26
View File
@@ -1,26 +0,0 @@
import { type App, ModuleManagerEvents, type AppPlugin } from "bknd";
import type { $diff } from "bknd/core";
export type SyncDiffsOptions = {
enabled?: boolean;
write: (name: string, diffs: $diff.DiffEntry[]) => Promise<void>;
};
export function syncDiffs({ enabled = true, write }: SyncDiffsOptions): AppPlugin {
return (app: App) => ({
name: "bknd-sync-diffs",
onBuilt: async () => {
if (!enabled) return;
app.emgr.onEvent(
ModuleManagerEvents.ModuleManagerConfigDiffEvent,
async (event) => {
const name = `${new Date().getTime()}.diff.json`;
await write?.(name, event.params.diffs);
},
{
id: "sync-config",
},
);
},
});
}
-1
View File
@@ -5,4 +5,3 @@ export {
export { showRoutes, type ShowRoutesOptions } from "./dev/show-routes.plugin"; export { showRoutes, type ShowRoutesOptions } from "./dev/show-routes.plugin";
export { syncConfig, type SyncConfigOptions } from "./dev/sync-config.plugin"; export { syncConfig, type SyncConfigOptions } from "./dev/sync-config.plugin";
export { syncTypes, type SyncTypesOptions } from "./dev/sync-types.plugin"; export { syncTypes, type SyncTypesOptions } from "./dev/sync-types.plugin";
export { syncDiffs, type SyncDiffsOptions } from "./dev/sync-diffs.plugin";
@@ -13,7 +13,7 @@ import {
import { useBknd } from "ui/client/bknd"; import { useBknd } from "ui/client/bknd";
import type { TSchemaActions } from "ui/client/schema/actions"; import type { TSchemaActions } from "ui/client/schema/actions";
import { bkndModals } from "ui/modals"; import { bkndModals } from "ui/modals";
import { s, parse, InvalidSchemaError } from "bknd/core"; import { s, parse, InvalidSchemaError } from "core/object/schema";
export function useBkndData() { export function useBkndData() {
const { config, app, schema, actions: bkndActions } = useBknd(); const { config, app, schema, actions: bkndActions } = useBknd();
+1 -1
View File
@@ -1,4 +1,4 @@
import { parse } from "bknd/core"; import { parse } from "core/object/schema";
import { type TAppFlowSchema, flowSchema } from "flows/flows-schema"; import { type TAppFlowSchema, flowSchema } from "flows/flows-schema";
import { useBknd } from "../../BkndProvider"; import { useBknd } from "../../BkndProvider";
@@ -64,12 +64,13 @@ const FieldImpl = ({
const id = `${name}-${useId()}`; const id = `${name}-${useId()}`;
const required = typeof _required === "boolean" ? _required : ctx.required; const required = typeof _required === "boolean" ? _required : ctx.required;
if (!schema) if (!schema) {
return ( return (
<Pre> <Pre>
[Field] {path} has no schema ({JSON.stringify(schema)}) [Field] {path} has no schema ({JSON.stringify(schema)})
</Pre> </Pre>
); );
}
if (isType(schema.type, "object")) { if (isType(schema.type, "object")) {
return <ObjectField path={name} />; return <ObjectField path={name} />;
@@ -23,7 +23,7 @@ export class JsonvTsValidator<T = any, S extends StrictRJSFSchema = RJSFSchema>
return { errors: [], validationError: null as any }; return { errors: [], validationError: null as any };
} }
const jsSchema = s.fromSchema(JSON.parse(JSON.stringify(schema)) as any); const jsSchema = s.fromSchema(schema as any);
const result = jsSchema.validate(formData); const result = jsSchema.validate(formData);
if (result.valid) { if (result.valid) {
+1 -1
View File
@@ -7,7 +7,7 @@ import { Button } from "ui/components/buttons/Button";
import { Group, Input, Password, Label } from "ui/components/form/Formy/components"; import { Group, Input, Password, Label } from "ui/components/form/Formy/components";
import { SocialLink } from "./SocialLink"; import { SocialLink } from "./SocialLink";
import type { Validator } from "json-schema-form-react"; import type { Validator } from "json-schema-form-react";
import { s } from "bknd/core"; import { s } from "core/object/schema";
import type { ErrorDetail } from "jsonv-ts"; import type { ErrorDetail } from "jsonv-ts";
class JsonvTsValidator implements Validator<ErrorDetail> { class JsonvTsValidator implements Validator<ErrorDetail> {
+1 -1
View File
@@ -1,7 +1,7 @@
import { decodeSearch, encodeSearch, mergeObject } from "core/utils"; import { decodeSearch, encodeSearch, mergeObject } from "core/utils";
import { isEqual, transform } from "lodash-es"; import { isEqual, transform } from "lodash-es";
import { useLocation, useSearch as useWouterSearch } from "wouter"; import { useLocation, useSearch as useWouterSearch } from "wouter";
import { type s, parse } from "bknd/core"; import { type s, parse } from "core/object/schema";
import { useEffect, useMemo, useState } from "react"; import { useEffect, useMemo, useState } from "react";
export type UseSearchOptions<Schema extends s.Schema = s.Schema> = { export type UseSearchOptions<Schema extends s.Schema = s.Schema> = {
+1 -1
View File
@@ -65,7 +65,7 @@ export function SchemaFormModal({
<JsonSchemaForm <JsonSchemaForm
tagName="form" tagName="form"
ref={formRef} ref={formRef}
schema={JSON.parse(JSON.stringify(schema))} schema={schema}
uiSchema={uiSchema} uiSchema={uiSchema}
className="legacy hide-required-mark fieldset-alternative mute-root" className="legacy hide-required-mark fieldset-alternative mute-root"
onChange={handleChange} onChange={handleChange}
@@ -1,5 +1,6 @@
import type { ModalProps } from "@mantine/core"; import type { ModalProps } from "@mantine/core";
import type { ContextModalProps } from "@mantine/modals"; import type { ContextModalProps } from "@mantine/modals";
import { entitiesSchema, fieldsSchema, relationsSchema } from "data/data-schema";
import { useState } from "react"; import { useState } from "react";
import { type Modal2Ref, ModalBody, ModalFooter, ModalTitle } from "ui/components/modal/Modal2"; import { type Modal2Ref, ModalBody, ModalFooter, ModalTitle } from "ui/components/modal/Modal2";
import { Step, Steps, useStepContext } from "ui/components/steps/Steps"; import { Step, Steps, useStepContext } from "ui/components/steps/Steps";
@@ -9,10 +10,52 @@ import { StepEntityFields } from "./step.entity.fields";
import { StepRelation } from "./step.relation"; import { StepRelation } from "./step.relation";
import { StepSelect } from "./step.select"; import { StepSelect } from "./step.select";
import Templates from "./templates/register"; import Templates from "./templates/register";
import type { TCreateModalSchema } from "./schema"; import { s } from "core/object/schema";
export type CreateModalRef = Modal2Ref; export type CreateModalRef = Modal2Ref;
export const ModalActions = ["entity", "relation", "media"] as const;
export const entitySchema = s.object({
name: s.string(),
...entitiesSchema.properties,
});
// @todo: this union is not fully working, just "string"
const schemaAction = s.anyOf([
s.string({ enum: ["entity", "relation", "media"] }),
s.string({ pattern: "^template-" }),
]);
export type TSchemaAction = s.Static<typeof schemaAction>;
const createFieldSchema = s.object({
entity: s.string(),
name: s.string(),
field: s.array(fieldsSchema),
});
export type TFieldCreate = s.Static<typeof createFieldSchema>;
const createModalSchema = s.strictObject({
action: schemaAction,
initial: s.any().optional(),
entities: s
.object({
create: s.array(entitySchema).optional(),
})
.optional(),
relations: s
.object({
create: s.array(s.anyOf(relationsSchema)).optional(),
})
.optional(),
fields: s
.object({
create: s.array(createFieldSchema).optional(),
})
.optional(),
});
export type TCreateModalSchema = s.Static<typeof createModalSchema>;
export function CreateModal({ export function CreateModal({
context, context,
id, id,
@@ -64,4 +107,4 @@ CreateModal.modalProps = {
padding: 0, padding: 0,
} satisfies Partial<ModalProps>; } satisfies Partial<ModalProps>;
export { ModalBody, ModalFooter, ModalTitle, useStepContext }; export { ModalBody, ModalFooter, ModalTitle, useStepContext, relationsSchema };
@@ -1,44 +0,0 @@
import { s } from "bknd/core";
import { entitiesSchema, fieldsSchema, relationsSchema } from "data/data-schema";
export const ModalActions = ["entity", "relation", "media"] as const;
export const entitySchema = s.object({
...entitiesSchema.properties,
name: s.string(),
});
// @todo: this union is not fully working, just "string"
const schemaAction = s.anyOf([
s.string({ enum: ["entity", "relation", "media"] }),
s.string({ pattern: "^template-" }),
]);
export type TSchemaAction = s.Static<typeof schemaAction>;
const createFieldSchema = s.object({
entity: s.string(),
name: s.string(),
field: s.array(fieldsSchema),
});
export type TFieldCreate = s.Static<typeof createFieldSchema>;
const createModalSchema = s.strictObject({
action: schemaAction,
initial: s.any().optional(),
entities: s
.object({
create: s.array(entitySchema).optional(),
})
.optional(),
relations: s
.object({
create: s.array(s.anyOf(relationsSchema)).optional(),
})
.optional(),
fields: s
.object({
create: s.array(createFieldSchema).optional(),
})
.optional(),
});
export type TCreateModalSchema = s.Static<typeof createModalSchema>;
@@ -1,6 +1,7 @@
import { useDisclosure } from "@mantine/hooks"; import { useDisclosure } from "@mantine/hooks";
import { import {
IconAlignJustified, IconAlignJustified,
IconAugmentedReality,
IconBox, IconBox,
IconCirclesRelation, IconCirclesRelation,
IconInfoCircle, IconInfoCircle,
@@ -14,7 +15,7 @@ import { IconButton, type IconType } from "ui/components/buttons/IconButton";
import { JsonViewer } from "ui/components/code/JsonViewer"; import { JsonViewer } from "ui/components/code/JsonViewer";
import { ModalBody, ModalFooter } from "ui/components/modal/Modal2"; import { ModalBody, ModalFooter } from "ui/components/modal/Modal2";
import { useStepContext } from "ui/components/steps/Steps"; import { useStepContext } from "ui/components/steps/Steps";
import type { TCreateModalSchema } from "ui/modules/data/components/schema/create-modal/schema"; import type { TCreateModalSchema } from "ui/modules/data/components/schema/create-modal/CreateModal";
type ActionItem = SummaryItemProps & { type ActionItem = SummaryItemProps & {
run: () => Promise<boolean>; run: () => Promise<boolean>;
@@ -34,9 +35,9 @@ export function StepCreate() {
action: "add", action: "add",
Icon: IconBox, Icon: IconBox,
type: "Entity", type: "Entity",
name: entity.name!, name: entity.name,
json: entity, json: entity,
run: async () => await $data.actions.entity.add(entity.name!, entity), run: async () => await $data.actions.entity.add(entity.name, entity),
})), })),
); );
} }
@@ -1,3 +1,4 @@
//import { typeboxResolver } from "@hookform/resolvers/typebox";
import { objectCleanEmpty } from "core/utils"; import { objectCleanEmpty } from "core/utils";
import { type TAppDataEntityFields, entitiesSchema } from "data/data-schema"; import { type TAppDataEntityFields, entitiesSchema } from "data/data-schema";
import { mergeWith } from "lodash-es"; import { mergeWith } from "lodash-es";
@@ -9,13 +10,12 @@ import {
EntityFieldsForm, EntityFieldsForm,
type EntityFieldsFormRef, type EntityFieldsFormRef,
} from "ui/routes/data/forms/entity.fields.form"; } from "ui/routes/data/forms/entity.fields.form";
import { ModalBody, ModalFooter, useStepContext } from "./CreateModal"; import { ModalBody, ModalFooter, type TCreateModalSchema, useStepContext } from "./CreateModal";
import { useBkndData } from "ui/client/schema/data/use-bknd-data"; import { useBkndData } from "ui/client/schema/data/use-bknd-data";
import type { s } from "bknd/core"; import type { s } from "core/object/schema";
import { standardSchemaResolver } from "@hookform/resolvers/standard-schema"; import { standardSchemaResolver } from "@hookform/resolvers/standard-schema";
import { entitySchema, type TCreateModalSchema } from "./schema";
const schema = entitySchema; const schema = entitiesSchema;
type Schema = s.Static<typeof schema>; type Schema = s.Static<typeof schema>;
export function StepEntityFields() { export function StepEntityFields() {
@@ -2,29 +2,30 @@ import { standardSchemaResolver } from "@hookform/resolvers/standard-schema";
import { TextInput, Textarea } from "@mantine/core"; import { TextInput, Textarea } from "@mantine/core";
import { useFocusTrap } from "@mantine/hooks"; import { useFocusTrap } from "@mantine/hooks";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { ModalBody, ModalFooter, useStepContext } from "./CreateModal"; import {
import { entitySchema, type TCreateModalSchema } from "./schema"; ModalBody,
import { s } from "bknd/core"; ModalFooter,
import { cloneSchema } from "core/object/schema"; type TCreateModalSchema,
entitySchema,
const schema = s.object({ useStepContext,
name: entitySchema.properties.name, } from "./CreateModal";
config: entitySchema.properties.config.partial().optional(),
});
type Schema = s.Static<typeof schema>;
export function StepEntity() { export function StepEntity() {
const focusTrapRef = useFocusTrap(); const focusTrapRef = useFocusTrap();
const { nextStep, stepBack, state, setState } = useStepContext<TCreateModalSchema>(); const { nextStep, stepBack, state, setState } = useStepContext<TCreateModalSchema>();
const { register, handleSubmit, formState, watch, control } = useForm({ const { register, handleSubmit, formState, watch, control } = useForm({
mode: "onChange", mode: "onTouched",
resolver: standardSchemaResolver(cloneSchema(schema)), resolver: standardSchemaResolver(entitySchema),
defaultValues: (state.entities?.create?.[0] ?? {}) as Schema, defaultValues: state.entities?.create?.[0] ?? {},
}); });
/*const data = watch();
console.log("state", { isValid });
console.log("schema", JSON.stringify(entitySchema));
console.log("data", JSON.stringify(data));*/
function onSubmit(data: any) { function onSubmit(data: any) {
console.log("onSubmit", data); console.log(data);
setState((prev) => { setState((prev) => {
const prevEntity = prev.entities?.create?.[0]; const prevEntity = prev.entities?.create?.[0];
if (prevEntity && prevEntity.name !== data.name) { if (prevEntity && prevEntity.name !== data.name) {
@@ -44,7 +45,6 @@ export function StepEntity() {
<> <>
<form onSubmit={handleSubmit(onSubmit)} ref={focusTrapRef}> <form onSubmit={handleSubmit(onSubmit)} ref={focusTrapRef}>
<ModalBody> <ModalBody>
<input type="hidden" {...register("type")} defaultValue="regular" />
<TextInput <TextInput
data-autofocus data-autofocus
required required

Some files were not shown because too many files have changed in this diff Show More