Merge pull request #34 from bknd-io/refactor/adapter-options

Refactor: Unified framework and runtime adapters
This commit is contained in:
dswbx
2024-12-24 09:45:03 +01:00
committed by GitHub
20 changed files with 276 additions and 253 deletions

View File

@@ -3,7 +3,7 @@
"type": "module", "type": "module",
"sideEffects": false, "sideEffects": false,
"bin": "./dist/cli/index.js", "bin": "./dist/cli/index.js",
"version": "0.4.0-rc1", "version": "0.4.0-rc2",
"scripts": { "scripts": {
"build:all": "NODE_ENV=production bun run build.ts --minify --types --clean && bun run build:cli", "build:all": "NODE_ENV=production bun run build.ts --minify --types --clean && bun run build:cli",
"dev": "vite", "dev": "vite",

View File

@@ -1,4 +1,7 @@
import { Api, type ApiOptions, App, type CreateAppConfig } from "bknd"; import { type FrameworkBkndConfig, createFrameworkApp } from "adapter";
import { Api, type ApiOptions, type App } from "bknd";
export type AstroBkndConfig = FrameworkBkndConfig;
type TAstro = { type TAstro = {
request: Request; request: Request;
@@ -18,12 +21,10 @@ export function getApi(Astro: TAstro, options: Options = { mode: "static" }) {
} }
let app: App; let app: App;
export function serve(config: CreateAppConfig & { beforeBuild?: (app: App) => Promise<void> }) { export function serve(config: AstroBkndConfig = {}) {
return async (args: TAstro) => { return async (args: TAstro) => {
if (!app) { if (!app) {
app = App.create(config); app = await createFrameworkApp(config);
await config.beforeBuild?.(app);
await app.build();
} }
return app.fetch(args.request); return app.fetch(args.request);
}; };

View File

@@ -1,64 +1,47 @@
/// <reference types="bun-types" /> /// <reference types="bun-types" />
import path from "node:path"; import path from "node:path";
import { App, type CreateAppConfig, registries } from "bknd"; import type { App } from "bknd";
import type { Serve, ServeOptions } from "bun"; import type { ServeOptions } from "bun";
import { config } from "core";
import { serveStatic } from "hono/bun"; import { serveStatic } from "hono/bun";
import { registerLocalMediaAdapter } from "../index"; import { type RuntimeBkndConfig, createRuntimeApp } from "../index";
let app: App; let app: App;
export type ExtendedAppCreateConfig = Partial<CreateAppConfig> & {
distPath?: string; export type BunBkndConfig = RuntimeBkndConfig & Omit<ServeOptions, "fetch">;
onBuilt?: (app: App) => Promise<void>;
buildOptions?: Parameters<App["build"]>[0];
};
export async function createApp({ export async function createApp({
distPath, distPath,
onBuilt, onBuilt,
buildOptions, buildConfig,
beforeBuild,
...config ...config
}: ExtendedAppCreateConfig) { }: RuntimeBkndConfig = {}) {
registerLocalMediaAdapter();
const root = path.resolve(distPath ?? "./node_modules/bknd/dist", "static"); const root = path.resolve(distPath ?? "./node_modules/bknd/dist", "static");
if (!app) { if (!app) {
app = App.create(config); app = await createRuntimeApp({
...config,
app.emgr.onEvent( registerLocalMedia: true,
App.Events.AppBuiltEvent, serveStatic: serveStatic({ root })
async () => { });
app.modules.server.get(
"/*",
serveStatic({
root
})
);
app.registerAdminController();
await onBuilt?.(app);
},
"sync"
);
await app.build(buildOptions);
} }
return app; return app;
} }
export type BunAdapterOptions = Omit<ServeOptions, "fetch"> & ExtendedAppCreateConfig;
export function serve({ export function serve({
distPath, distPath,
connection, connection,
initialConfig, initialConfig,
plugins, plugins,
options, options,
port = 1337, port = config.server.default_port,
onBuilt, onBuilt,
buildOptions, buildConfig,
...serveOptions ...serveOptions
}: BunAdapterOptions = {}) { }: BunBkndConfig = {}) {
Bun.serve({ Bun.serve({
...serveOptions, ...serveOptions,
port, port,
@@ -69,7 +52,7 @@ export function serve({
plugins, plugins,
options, options,
onBuilt, onBuilt,
buildOptions, buildConfig,
distPath distPath
}); });
return app.fetch(request); return app.fetch(request);

View File

@@ -1,22 +1,37 @@
import type { CreateAppConfig } from "bknd";
import { Hono } from "hono"; import { Hono } from "hono";
import { serveStatic } from "hono/cloudflare-workers"; import { serveStatic } from "hono/cloudflare-workers";
import type { BkndConfig } from "../index"; import type { FrameworkBkndConfig } from "../index";
import { getCached } from "./modes/cached"; import { getCached } from "./modes/cached";
import { getDurable } from "./modes/durable"; import { getDurable } from "./modes/durable";
import { getFresh, getWarm } from "./modes/fresh"; import { getFresh, getWarm } from "./modes/fresh";
export type CloudflareBkndConfig<Env = any> = Omit<FrameworkBkndConfig, "app"> & {
app: CreateAppConfig | ((env: Env) => CreateAppConfig);
mode?: "warm" | "fresh" | "cache" | "durable";
bindings?: (env: Env) => {
kv?: KVNamespace;
dobj?: DurableObjectNamespace;
};
key?: string;
keepAliveSeconds?: number;
forceHttps?: boolean;
manifest?: string;
setAdminHtml?: boolean;
html?: string;
};
export type Context = { export type Context = {
request: Request; request: Request;
env: any; env: any;
ctx: ExecutionContext; ctx: ExecutionContext;
manifest: any;
html?: string;
}; };
export function serve(_config: BkndConfig, manifest?: string, html?: string) { export function serve(config: CloudflareBkndConfig) {
return { return {
async fetch(request: Request, env: any, ctx: ExecutionContext) { async fetch(request: Request, env: any, ctx: ExecutionContext) {
const url = new URL(request.url); const url = new URL(request.url);
const manifest = config.manifest;
if (manifest) { if (manifest) {
const pathname = url.pathname.slice(1); const pathname = url.pathname.slice(1);
@@ -27,8 +42,7 @@ export function serve(_config: BkndConfig, manifest?: string, html?: string) {
hono.all("*", async (c, next) => { hono.all("*", async (c, next) => {
const res = await serveStatic({ const res = await serveStatic({
path: `./${pathname}`, path: `./${pathname}`,
manifest, manifest
onNotFound: (path) => console.log("not found", path)
})(c as any, next); })(c as any, next);
if (res instanceof Response) { if (res instanceof Response) {
const ttl = 60 * 60 * 24 * 365; const ttl = 60 * 60 * 24 * 365;
@@ -43,12 +57,10 @@ export function serve(_config: BkndConfig, manifest?: string, html?: string) {
} }
} }
const config = { config.setAdminHtml = config.setAdminHtml && !!config.manifest;
..._config,
setAdminHtml: _config.setAdminHtml ?? !!manifest const context = { request, env, ctx } as Context;
}; const mode = config.mode ?? "warm";
const context = { request, env, ctx, manifest, html } as Context;
const mode = config.cloudflare?.mode ?? "warm";
switch (mode) { switch (mode) {
case "fresh": case "fresh":

View File

@@ -1,38 +1,31 @@
import type { BkndConfig } from "adapter"; import { createRuntimeApp } from "adapter";
import { App } from "bknd"; import { App } from "bknd";
import type { Context } from "../index"; import type { CloudflareBkndConfig, Context } from "../index";
export async function getCached(config: BkndConfig, { env, html, ctx }: Context) { export async function getCached(config: CloudflareBkndConfig, { env, ctx }: Context) {
const { kv } = config.cloudflare?.bindings?.(env)!; const { kv } = config.bindings?.(env)!;
if (!kv) throw new Error("kv namespace is not defined in cloudflare.bindings"); if (!kv) throw new Error("kv namespace is not defined in cloudflare.bindings");
const key = config.cloudflare?.key ?? "app"; const key = config.key ?? "app";
const create_config = typeof config.app === "function" ? config.app(env) : config.app; const create_config = typeof config.app === "function" ? config.app(env) : config.app;
const cachedConfig = await kv.get(key); const cachedConfig = await kv.get(key);
const initialConfig = cachedConfig ? JSON.parse(cachedConfig) : undefined; const initialConfig = cachedConfig ? JSON.parse(cachedConfig) : undefined;
const app = App.create({ ...create_config, initialConfig });
async function saveConfig(__config: any) { async function saveConfig(__config: any) {
ctx.waitUntil(kv!.put(key, JSON.stringify(__config))); ctx.waitUntil(kv!.put(key, JSON.stringify(__config)));
} }
if (config.onBuilt) { const app = await createRuntimeApp({
app.emgr.onEvent( ...create_config,
App.Events.AppBuiltEvent, initialConfig,
async ({ params: { app } }) => { onBuilt: async (app) => {
app.module.server.client.get("/__bknd/cache", async (c) => { app.module.server.client.get("/__bknd/cache", async (c) => {
await kv.delete(key); await kv.delete(key);
return c.json({ message: "Cache cleared" }); return c.json({ message: "Cache cleared" });
}); });
app.registerAdminController({ html }); await config.onBuilt?.(app);
config.onBuilt!(app);
}, },
"sync" beforeBuild: async (app) => {
);
}
app.emgr.onEvent( app.emgr.onEvent(
App.Events.AppConfigUpdatedEvent, App.Events.AppConfigUpdatedEvent,
async ({ params: { app } }) => { async ({ params: { app } }) => {
@@ -40,12 +33,10 @@ export async function getCached(config: BkndConfig, { env, html, ctx }: Context)
}, },
"sync" "sync"
); );
await config.beforeBuild?.(app);
await app.build(); },
adminOptions: { html: config.html }
if (config.setAdminHtml) { });
app.registerAdminController({ html });
}
if (!cachedConfig) { if (!cachedConfig) {
saveConfig(app.toJSON(true)); saveConfig(app.toJSON(true));

View File

@@ -1,15 +1,15 @@
import { DurableObject } from "cloudflare:workers"; import { DurableObject } from "cloudflare:workers";
import type { BkndConfig } from "adapter"; import { createRuntimeApp } from "adapter";
import type { Context } from "adapter/cloudflare"; import type { CloudflareBkndConfig, Context } from "adapter/cloudflare";
import { App, type CreateAppConfig } from "bknd"; import type { App, CreateAppConfig } from "bknd";
export async function getDurable(config: BkndConfig, ctx: Context) { export async function getDurable(config: CloudflareBkndConfig, ctx: Context) {
const { dobj } = config.cloudflare?.bindings?.(ctx.env)!; const { dobj } = config.bindings?.(ctx.env)!;
if (!dobj) throw new Error("durable object is not defined in cloudflare.bindings"); if (!dobj) throw new Error("durable object is not defined in cloudflare.bindings");
const key = config.cloudflare?.key ?? "app"; const key = config.key ?? "app";
if (config.onBuilt) { if ([config.onBuilt, config.beforeBuild].some((x) => x)) {
console.log("onBuilt() is not supported with DurableObject mode"); console.log("onBuilt and beforeBuild are not supported with DurableObject mode");
} }
const start = performance.now(); const start = performance.now();
@@ -21,8 +21,8 @@ export async function getDurable(config: BkndConfig, ctx: Context) {
const res = await stub.fire(ctx.request, { const res = await stub.fire(ctx.request, {
config: create_config, config: create_config,
html: ctx.html, html: config.html,
keepAliveSeconds: config.cloudflare?.keepAliveSeconds, keepAliveSeconds: config.keepAliveSeconds,
setAdminHtml: config.setAdminHtml setAdminHtml: config.setAdminHtml
}); });
@@ -64,10 +64,9 @@ export class DurableBkndApp extends DurableObject {
config.connection.config.protocol = "wss"; config.connection.config.protocol = "wss";
} }
this.app = App.create(config); this.app = await createRuntimeApp({
this.app.emgr.onEvent( ...config,
App.Events.AppBuiltEvent, onBuilt: async (app) => {
async ({ params: { app } }) => {
app.modules.server.get("/__do", async (c) => { app.modules.server.get("/__do", async (c) => {
// @ts-ignore // @ts-ignore
const context: any = c.req.raw.cf ? c.req.raw.cf : c.env.cf; const context: any = c.req.raw.cf ? c.req.raw.cf : c.env.cf;
@@ -80,14 +79,11 @@ export class DurableBkndApp extends DurableObject {
await this.onBuilt(app); await this.onBuilt(app);
}, },
"sync" adminOptions: { html: options.html },
); beforeBuild: async (app) => {
await this.beforeBuild(app);
await this.app.build();
if (options.setAdminHtml) {
this.app.registerAdminController({ html: options.html });
} }
});
buildtime = performance.now() - start; buildtime = performance.now() - start;
} }
@@ -110,6 +106,7 @@ export class DurableBkndApp extends DurableObject {
} }
async onBuilt(app: App) {} async onBuilt(app: App) {}
async beforeBuild(app: App) {}
protected keepAlive(seconds: number) { protected keepAlive(seconds: number) {
console.log("keep alive for", seconds); console.log("keep alive for", seconds);

View File

@@ -1,36 +1,23 @@
import type { BkndConfig } from "adapter"; import { createRuntimeApp } from "adapter";
import { App } from "bknd"; import type { App } from "bknd";
import type { Context } from "../index"; import type { CloudflareBkndConfig, Context } from "../index";
export async function makeApp(config: BkndConfig, { env, html }: Context) { export async function makeApp(config: CloudflareBkndConfig, { env }: Context) {
const create_config = typeof config.app === "function" ? config.app(env) : config.app; const create_config = typeof config.app === "function" ? config.app(env) : config.app;
const app = App.create(create_config); return await createRuntimeApp({
...config,
if (config.onBuilt) { ...create_config,
app.emgr.onEvent( adminOptions: config.html ? { html: config.html } : undefined
App.Events.AppBuiltEvent, });
async ({ params: { app } }) => {
config.onBuilt!(app);
},
"sync"
);
}
await app.build();
if (config.setAdminHtml) {
app.registerAdminController({ html });
}
return app;
} }
export async function getFresh(config: BkndConfig, ctx: Context) { export async function getFresh(config: CloudflareBkndConfig, ctx: Context) {
const app = await makeApp(config, ctx); const app = await makeApp(config, ctx);
return app.fetch(ctx.request); return app.fetch(ctx.request);
} }
let warm_app: App; let warm_app: App;
export async function getWarm(config: BkndConfig, ctx: Context) { export async function getWarm(config: CloudflareBkndConfig, ctx: Context) {
if (!warm_app) { if (!warm_app) {
warm_app = await makeApp(config, ctx); warm_app = await makeApp(config, ctx);
} }

View File

@@ -1,28 +1,19 @@
import type { IncomingMessage } from "node:http"; import type { IncomingMessage } from "node:http";
import { type App, type CreateAppConfig, registries } from "bknd"; import { App, type CreateAppConfig, registries } from "bknd";
import type { MiddlewareHandler } from "hono";
import { StorageLocalAdapter } from "media/storage/adapters/StorageLocalAdapter"; import { StorageLocalAdapter } from "media/storage/adapters/StorageLocalAdapter";
import type { AdminControllerOptions } from "modules/server/AdminController";
export type CloudflareBkndConfig<Env = any> = { type BaseExternalBkndConfig = CreateAppConfig & {
mode?: "warm" | "fresh" | "cache" | "durable"; onBuilt?: (app: App) => Promise<void>;
bindings?: (env: Env) => { beforeBuild?: (app: App) => Promise<void>;
kv?: KVNamespace; buildConfig?: Parameters<App["build"]>[0];
dobj?: DurableObjectNamespace;
};
key?: string;
keepAliveSeconds?: number;
forceHttps?: boolean;
}; };
// @todo: move to App export type FrameworkBkndConfig = BaseExternalBkndConfig;
export type BkndConfig<Env = any> = {
app: CreateAppConfig | ((env: Env) => CreateAppConfig); export type RuntimeBkndConfig = BaseExternalBkndConfig & {
setAdminHtml?: boolean; distPath?: string;
server?: {
port?: number;
platform?: "node" | "bun";
};
cloudflare?: CloudflareBkndConfig<Env>;
onBuilt?: (app: App) => Promise<void>;
}; };
export function nodeRequestToRequest(req: IncomingMessage): Request { export function nodeRequestToRequest(req: IncomingMessage): Request {
@@ -52,3 +43,64 @@ export function nodeRequestToRequest(req: IncomingMessage): Request {
export function registerLocalMediaAdapter() { export function registerLocalMediaAdapter() {
registries.media.register("local", StorageLocalAdapter); registries.media.register("local", StorageLocalAdapter);
} }
export async function createFrameworkApp(config: FrameworkBkndConfig): Promise<App> {
const app = App.create(config);
if (config.onBuilt) {
app.emgr.onEvent(
App.Events.AppBuiltEvent,
async () => {
await config.onBuilt?.(app);
},
"sync"
);
}
await config.beforeBuild?.(app);
await app.build(config.buildConfig);
return app;
}
export async function createRuntimeApp({
serveStatic,
registerLocalMedia,
adminOptions,
...config
}: RuntimeBkndConfig & {
serveStatic?: MiddlewareHandler | [string, MiddlewareHandler];
registerLocalMedia?: boolean;
adminOptions?: AdminControllerOptions | false;
}): Promise<App> {
if (registerLocalMedia) {
registerLocalMediaAdapter();
}
const app = App.create(config);
app.emgr.onEvent(
App.Events.AppBuiltEvent,
async () => {
if (serveStatic) {
if (Array.isArray(serveStatic)) {
const [path, handler] = serveStatic;
app.modules.server.get(path, handler);
} else {
app.modules.server.get("/*", serveStatic);
}
}
await config.onBuilt?.(app);
if (adminOptions !== false) {
app.registerAdminController(adminOptions);
}
},
"sync"
);
await config.beforeBuild?.(app);
await app.build(config.buildConfig);
return app;
}

View File

@@ -1,6 +1,8 @@
import type { IncomingMessage, ServerResponse } from "node:http"; import type { IncomingMessage, ServerResponse } from "node:http";
import { Api, App, type CreateAppConfig } from "bknd"; import { Api, type App } from "bknd";
import { nodeRequestToRequest } from "../index"; import { type FrameworkBkndConfig, createFrameworkApp, nodeRequestToRequest } from "../index";
export type NextjsBkndConfig = FrameworkBkndConfig;
type GetServerSidePropsContext = { type GetServerSidePropsContext = {
req: IncomingMessage; req: IncomingMessage;
@@ -42,12 +44,10 @@ function getCleanRequest(req: Request) {
} }
let app: App; let app: App;
export function serve(config: CreateAppConfig & { beforeBuild?: (app: App) => Promise<void> }) { export function serve(config: NextjsBkndConfig = {}) {
return async (req: Request) => { return async (req: Request) => {
if (!app) { if (!app) {
app = App.create(config); app = await createFrameworkApp(config);
await config.beforeBuild?.(app);
await app.build();
} }
const request = getCleanRequest(req); const request = getCleanRequest(req);
return app.fetch(request, process.env); return app.fetch(request, process.env);

View File

@@ -1,33 +1,37 @@
import path from "node:path"; import path from "node:path";
import { serve as honoServe } from "@hono/node-server"; 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 { App, type CreateAppConfig, registries } from "bknd"; import type { App } from "bknd";
import { registerLocalMediaAdapter } from "../index"; import { config as $config } from "core";
import { type RuntimeBkndConfig, createRuntimeApp } from "../index";
export type NodeAdapterOptions = CreateAppConfig & { export type NodeBkndConfig = RuntimeBkndConfig & {
relativeDistPath?: string;
port?: number; port?: number;
hostname?: string; hostname?: string;
listener?: Parameters<typeof honoServe>[1]; listener?: Parameters<typeof honoServe>[1];
onBuilt?: (app: App) => Promise<void>; /** @deprecated */
buildOptions?: Parameters<App["build"]>[0]; relativeDistPath?: string;
}; };
export function serve({ export function serve({
distPath,
relativeDistPath, relativeDistPath,
port = 1337, port = $config.server.default_port,
hostname, hostname,
listener, listener,
onBuilt, onBuilt,
buildOptions = {}, buildConfig = {},
beforeBuild,
...config ...config
}: NodeAdapterOptions = {}) { }: NodeBkndConfig = {}) {
registerLocalMediaAdapter();
const root = path.relative( const root = path.relative(
process.cwd(), process.cwd(),
path.resolve(relativeDistPath ?? "./node_modules/bknd/dist", "static") path.resolve(distPath ?? relativeDistPath ?? "./node_modules/bknd/dist", "static")
); );
if (relativeDistPath) {
console.warn("relativeDistPath is deprecated, please use distPath instead");
}
let app: App; let app: App;
honoServe( honoServe(
@@ -36,24 +40,11 @@ export function serve({
hostname, hostname,
fetch: async (req: Request) => { fetch: async (req: Request) => {
if (!app) { if (!app) {
app = App.create(config); app = await createRuntimeApp({
...config,
app.emgr.onEvent( registerLocalMedia: true,
App.Events.AppBuiltEvent, serveStatic: serveStatic({ root })
async () => { });
app.modules.server.get(
"/*",
serveStatic({
root
})
);
app.registerAdminController();
await onBuilt?.(app);
},
"sync"
);
await app.build(buildOptions);
} }
return app.fetch(req); return app.fetch(req);

View File

@@ -1,12 +1,13 @@
import { App, type CreateAppConfig } from "bknd"; import { type FrameworkBkndConfig, createFrameworkApp } from "adapter";
import type { App } from "bknd";
export type RemixBkndConfig = FrameworkBkndConfig;
let app: App; let app: App;
export function serve(config: CreateAppConfig & { beforeBuild?: (app: App) => Promise<void> }) { export function serve(config: RemixBkndConfig = {}) {
return async (args: { request: Request }) => { return async (args: { request: Request }) => {
if (!app) { if (!app) {
app = App.create(config); app = await createFrameworkApp(config);
await config.beforeBuild?.(app);
await app.build();
} }
return app.fetch(args.request); return app.fetch(args.request);
}; };

View File

@@ -1,47 +1,41 @@
import { serveStatic } from "@hono/node-server/serve-static"; import { serveStatic } from "@hono/node-server/serve-static";
import type { BkndConfig } from "bknd"; import { type RuntimeBkndConfig, createRuntimeApp } from "adapter";
import { App } from "bknd"; import type { CreateAppConfig } from "bknd";
import type { App } from "bknd";
function createApp(config: BkndConfig, env: any) { export type ViteBkndConfig<Env = any> = RuntimeBkndConfig & {
app: CreateAppConfig | ((env: Env) => CreateAppConfig);
setAdminHtml?: boolean;
forceDev?: boolean;
html?: string;
};
async function createApp(config: ViteBkndConfig, env: any) {
const create_config = typeof config.app === "function" ? config.app(env) : config.app; const create_config = typeof config.app === "function" ? config.app(env) : config.app;
return App.create(create_config); return await createRuntimeApp({
...create_config,
adminOptions: config.setAdminHtml
? { html: config.html, forceDev: config.forceDev }
: undefined,
serveStatic: ["/assets/*", serveStatic({ root: config.distPath ?? "./" })]
});
} }
function setAppBuildListener(app: App, config: BkndConfig, html?: string) { export async function serveFresh(config: ViteBkndConfig) {
app.emgr.onEvent(
App.Events.AppBuiltEvent,
async () => {
await config.onBuilt?.(app);
if (config.setAdminHtml) {
app.registerAdminController({ html, forceDev: true });
app.module.server.client.get("/assets/*", serveStatic({ root: "./" }));
}
},
"sync"
);
}
export async function serveFresh(config: BkndConfig, _html?: string) {
return { return {
async fetch(request: Request, env: any, ctx: ExecutionContext) { async fetch(request: Request, env: any, ctx: ExecutionContext) {
const app = createApp(config, env); const app = await createApp(config, env);
setAppBuildListener(app, config, _html);
await app.build();
return app.fetch(request, env, ctx); return app.fetch(request, env, ctx);
} }
}; };
} }
let app: App; let app: App;
export async function serveCached(config: BkndConfig, _html?: string) { export async function serveCached(config: ViteBkndConfig) {
return { return {
async fetch(request: Request, env: any, ctx: ExecutionContext) { async fetch(request: Request, env: any, ctx: ExecutionContext) {
if (!app) { if (!app) {
app = createApp(config, env); app = await createApp(config, env);
setAppBuildListener(app, config, _html);
await app.build();
} }
return app.fetch(request, env, ctx); return app.fetch(request, env, ctx);

View File

@@ -287,14 +287,18 @@ export class AppAuth extends Module<typeof authConfigSchema> {
} catch (e) {} } catch (e) {}
} }
async createUser(input: { email: string; password: string }) { async createUser({
email,
password,
...additional
}: { email: string; password: string; [key: string]: any }) {
const strategy = "password"; const strategy = "password";
const pw = this.authenticator.strategy(strategy) as PasswordStrategy; const pw = this.authenticator.strategy(strategy) as PasswordStrategy;
const strategy_value = await pw.hash(input.password); const strategy_value = await pw.hash(password);
const mutator = this.em.mutator(this.config.entity_name as "users"); const mutator = this.em.mutator(this.config.entity_name as "users");
mutator.__unstable_toggleSystemEntityCreation(false); mutator.__unstable_toggleSystemEntityCreation(false);
const { data: created } = await mutator.insertOne({ const { data: created } = await mutator.insertOne({
email: input.email, ...(additional as any),
strategy, strategy,
strategy_value strategy_value
}); });

View File

@@ -1,9 +1,9 @@
import type { Config } from "@libsql/client/node"; import type { Config } from "@libsql/client/node";
import { App, type CreateAppConfig } from "App"; import { App, type CreateAppConfig } from "App";
import type { BkndConfig } from "adapter";
import { StorageLocalAdapter } from "adapter/node"; import { StorageLocalAdapter } from "adapter/node";
import type { CliCommand } from "cli/types"; import type { CliBkndConfig, CliCommand } from "cli/types";
import { Option } from "commander"; import { Option } from "commander";
import { config } from "core";
import { registries } from "modules/registries"; import { registries } from "modules/registries";
import { import {
PLATFORMS, PLATFORMS,
@@ -21,7 +21,7 @@ export const run: CliCommand = (program) => {
.addOption( .addOption(
new Option("-p, --port <port>", "port to run on") new Option("-p, --port <port>", "port to run on")
.env("PORT") .env("PORT")
.default(1337) .default(config.server.default_port)
.argParser((v) => Number.parseInt(v)) .argParser((v) => Number.parseInt(v))
) )
.addOption(new Option("-c, --config <config>", "config file")) .addOption(new Option("-c, --config <config>", "config file"))
@@ -72,7 +72,7 @@ async function makeApp(config: MakeAppConfig) {
return app; return app;
} }
export async function makeConfigApp(config: BkndConfig, platform?: Platform) { export async function makeConfigApp(config: CliBkndConfig, platform?: Platform) {
const appConfig = typeof config.app === "function" ? config.app(process.env) : config.app; const appConfig = typeof config.app === "function" ? config.app(process.env) : config.app;
const app = App.create(appConfig); const app = App.create(appConfig);
@@ -82,14 +82,13 @@ export async function makeConfigApp(config: BkndConfig, platform?: Platform) {
await attachServeStatic(app, platform ?? "node"); await attachServeStatic(app, platform ?? "node");
app.registerAdminController(); app.registerAdminController();
if (config.onBuilt) { await config.onBuilt?.(app);
await config.onBuilt(app);
}
}, },
"sync" "sync"
); );
await app.build(); await config.beforeBuild?.(app);
await app.build(config.buildConfig);
return app; return app;
} }
@@ -110,7 +109,7 @@ async function action(options: {
app = await makeApp({ connection, server: { platform: options.server } }); app = await makeApp({ connection, server: { platform: options.server } });
} else { } else {
console.log("Using config from:", configFilePath); console.log("Using config from:", configFilePath);
const config = (await import(configFilePath).then((m) => m.default)) as BkndConfig; const config = (await import(configFilePath).then((m) => m.default)) as CliBkndConfig;
app = await makeConfigApp(config, options.server); app = await makeConfigApp(config, options.server);
} }

View File

@@ -1,10 +1,9 @@
import { password as $password, text as $text } from "@clack/prompts"; import { password as $password, text as $text } from "@clack/prompts";
import type { App } from "App"; import type { App } from "App";
import type { BkndConfig } from "adapter";
import type { PasswordStrategy } from "auth/authenticate/strategies"; import type { PasswordStrategy } from "auth/authenticate/strategies";
import { makeConfigApp } from "cli/commands/run"; import { makeConfigApp } from "cli/commands/run";
import { getConfigPath } from "cli/commands/run/platform"; import { getConfigPath } from "cli/commands/run/platform";
import type { CliCommand } from "cli/types"; import type { CliBkndConfig, CliCommand } from "cli/types";
import { Argument } from "commander"; import { Argument } from "commander";
export const user: CliCommand = (program) => { export const user: CliCommand = (program) => {
@@ -22,7 +21,7 @@ async function action(action: "create" | "update", options: any) {
return; return;
} }
const config = (await import(configFilePath).then((m) => m.default)) as BkndConfig; const config = (await import(configFilePath).then((m) => m.default)) as CliBkndConfig;
const app = await makeConfigApp(config, options.server); const app = await makeConfigApp(config, options.server);
switch (action) { switch (action) {

View File

@@ -1,3 +1,14 @@
import type { CreateAppConfig } from "App";
import type { FrameworkBkndConfig } from "adapter";
import type { Command } from "commander"; import type { Command } from "commander";
export type CliCommand = (program: Command) => void; export type CliCommand = (program: Command) => void;
export type CliBkndConfig<Env = any> = FrameworkBkndConfig & {
app: CreateAppConfig | ((env: Env) => CreateAppConfig);
setAdminHtml?: boolean;
server?: {
port?: number;
platform?: "node" | "bun";
};
};

View File

@@ -9,6 +9,9 @@ export type PrimaryFieldType = number | Generated<number>;
export interface DB {} export interface DB {}
export const config = { export const config = {
server: {
default_port: 1337
},
data: { data: {
default_primary_field: "id" default_primary_field: "id"
} }

View File

@@ -1,4 +1,4 @@
import { Api, App } from "bknd"; import { App } from "bknd";
import { serve } from "bknd/adapter/astro"; import { serve } from "bknd/adapter/astro";
import { registerLocalMediaAdapter } from "bknd/adapter/node"; import { registerLocalMediaAdapter } from "bknd/adapter/node";
import { boolean, em, entity, text } from "bknd/data"; import { boolean, em, entity, text } from "bknd/data";

View File

@@ -2,8 +2,7 @@ import { serve } from "bknd/adapter/cloudflare";
import manifest from "__STATIC_CONTENT_MANIFEST"; import manifest from "__STATIC_CONTENT_MANIFEST";
export default serve( export default serve({
{
app: (env: Env) => ({ app: (env: Env) => ({
connection: { connection: {
type: "libsql", type: "libsql",
@@ -13,8 +12,7 @@ export default serve(
} }
}), }),
onBuilt: async (app) => { onBuilt: async (app) => {
app.modules.server.get("/hello", (c) => c.json({ hello: "world" })); app.modules.server.get("/custom", (c) => c.json({ hello: "world" }));
}
}, },
manifest manifest
); });

View File

@@ -4,7 +4,7 @@ import { serve } from "bknd/adapter/node";
// serve(); // serve();
// this is optional, if omitted, it uses an in-memory database // this is optional, if omitted, it uses an in-memory database
/** @type {import("bknd/adapter/node").NodeAdapterOptions} */ /** @type {import("bknd/adapter/node").NodeBkndConfig} */
const config = { const config = {
connection: { connection: {
type: "libsql", type: "libsql",
@@ -14,7 +14,7 @@ const config = {
}, },
// this is only required to run inside the same workspace // this is only required to run inside the same workspace
// leave blank if you're running this from a different project // leave blank if you're running this from a different project
relativeDistPath: "../../app/dist" distPath: "../../app/dist"
}; };
serve(config); serve(config);