mirror of
https://github.com/shishantbiswas/bknd.git
synced 2026-03-16 04:27:21 +00:00
Merge pull request #25 from bknd-io/feat/node-media-local
Feat: Node media local
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { describe, test } from "bun:test";
|
||||
import { describe, expect, test } from "bun:test";
|
||||
import type { TObject, TString } from "@sinclair/typebox";
|
||||
import { Registry } from "../../src/core/registry/Registry";
|
||||
import { type TSchema, Type } from "../../src/core/utils";
|
||||
@@ -11,6 +11,9 @@ class What {
|
||||
method() {
|
||||
return null;
|
||||
}
|
||||
getType() {
|
||||
return Type.Object({ type: Type.String() });
|
||||
}
|
||||
}
|
||||
class What2 extends What {}
|
||||
class NotAllowed {}
|
||||
@@ -32,25 +35,53 @@ describe("Registry", () => {
|
||||
} satisfies Record<string, Test1>);
|
||||
|
||||
const item = registry.get("first");
|
||||
expect(item).toBeDefined();
|
||||
expect(item?.cls).toBe(What);
|
||||
|
||||
const second = Type.Object({ type: Type.String(), what: Type.String() });
|
||||
registry.add("second", {
|
||||
cls: What2,
|
||||
schema: Type.Object({ type: Type.String(), what: Type.String() }),
|
||||
schema: second,
|
||||
enabled: true
|
||||
});
|
||||
// @ts-ignore
|
||||
expect(registry.get("second").schema).toEqual(second);
|
||||
|
||||
const third = Type.Object({ type: Type.String({ default: "1" }), what22: Type.String() });
|
||||
registry.add("third", {
|
||||
// @ts-expect-error
|
||||
cls: NotAllowed,
|
||||
schema: Type.Object({ type: Type.String({ default: "1" }), what22: Type.String() }),
|
||||
schema: third,
|
||||
enabled: true
|
||||
});
|
||||
// @ts-ignore
|
||||
expect(registry.get("third").schema).toEqual(third);
|
||||
|
||||
const fourth = Type.Object({ type: Type.Number(), what22: Type.String() });
|
||||
registry.add("fourth", {
|
||||
cls: What,
|
||||
// @ts-expect-error
|
||||
schema: Type.Object({ type: Type.Number(), what22: Type.String() }),
|
||||
schema: fourth,
|
||||
enabled: true
|
||||
});
|
||||
// @ts-ignore
|
||||
expect(registry.get("fourth").schema).toEqual(fourth);
|
||||
|
||||
console.log("list", registry.all());
|
||||
expect(Object.keys(registry.all()).length).toBe(4);
|
||||
});
|
||||
|
||||
test("uses registration fn", async () => {
|
||||
const registry = new Registry<Test1>((a: ClassRef<What>) => {
|
||||
return {
|
||||
cls: a,
|
||||
schema: a.prototype.getType(),
|
||||
enabled: true
|
||||
};
|
||||
});
|
||||
|
||||
registry.register("what2", What2);
|
||||
expect(registry.get("what2")).toBeDefined();
|
||||
expect(registry.get("what2").cls).toBe(What2);
|
||||
expect(registry.get("what2").schema).toEqual(What2.prototype.getType());
|
||||
});
|
||||
});
|
||||
|
||||
@@ -6,14 +6,25 @@ import type { Serve, ServeOptions } from "bun";
|
||||
import { serveStatic } from "hono/bun";
|
||||
|
||||
let app: App;
|
||||
export async function createApp(_config: Partial<CreateAppConfig> = {}, distPath?: string) {
|
||||
export type ExtendedAppCreateConfig = Partial<CreateAppConfig> & {
|
||||
distPath?: string;
|
||||
onBuilt?: (app: App) => Promise<void>;
|
||||
buildOptions?: Parameters<App["build"]>[0];
|
||||
};
|
||||
|
||||
export async function createApp({
|
||||
distPath,
|
||||
onBuilt,
|
||||
buildOptions,
|
||||
...config
|
||||
}: ExtendedAppCreateConfig) {
|
||||
const root = path.resolve(distPath ?? "./node_modules/bknd/dist", "static");
|
||||
|
||||
if (!app) {
|
||||
app = App.create(_config);
|
||||
app = App.create(config);
|
||||
|
||||
app.emgr.on(
|
||||
"app-built",
|
||||
app.emgr.onEvent(
|
||||
App.Events.AppBuiltEvent,
|
||||
async () => {
|
||||
app.modules.server.get(
|
||||
"/*",
|
||||
@@ -22,20 +33,18 @@ export async function createApp(_config: Partial<CreateAppConfig> = {}, distPath
|
||||
})
|
||||
);
|
||||
app.registerAdminController();
|
||||
await onBuilt?.(app);
|
||||
},
|
||||
"sync"
|
||||
);
|
||||
|
||||
await app.build();
|
||||
await app.build(buildOptions);
|
||||
}
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
export type BunAdapterOptions = Omit<ServeOptions, "fetch"> &
|
||||
CreateAppConfig & {
|
||||
distPath?: string;
|
||||
};
|
||||
export type BunAdapterOptions = Omit<ServeOptions, "fetch"> & ExtendedAppCreateConfig;
|
||||
|
||||
export function serve({
|
||||
distPath,
|
||||
@@ -44,13 +53,23 @@ export function serve({
|
||||
plugins,
|
||||
options,
|
||||
port = 1337,
|
||||
onBuilt,
|
||||
buildOptions,
|
||||
...serveOptions
|
||||
}: BunAdapterOptions = {}) {
|
||||
Bun.serve({
|
||||
...serveOptions,
|
||||
port,
|
||||
fetch: async (request: Request) => {
|
||||
const app = await createApp({ connection, initialConfig, plugins, options }, distPath);
|
||||
const app = await createApp({
|
||||
connection,
|
||||
initialConfig,
|
||||
plugins,
|
||||
options,
|
||||
onBuilt,
|
||||
buildOptions,
|
||||
distPath
|
||||
});
|
||||
return app.fetch(request);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,59 +1,5 @@
|
||||
import path from "node:path";
|
||||
import { serve as honoServe } from "@hono/node-server";
|
||||
import { serveStatic } from "@hono/node-server/serve-static";
|
||||
import { App, type CreateAppConfig } from "bknd";
|
||||
|
||||
export type NodeAdapterOptions = CreateAppConfig & {
|
||||
relativeDistPath?: string;
|
||||
port?: number;
|
||||
hostname?: string;
|
||||
listener?: Parameters<typeof honoServe>[1];
|
||||
};
|
||||
|
||||
export function serve({
|
||||
relativeDistPath,
|
||||
port = 1337,
|
||||
hostname,
|
||||
listener,
|
||||
...config
|
||||
}: NodeAdapterOptions = {}) {
|
||||
const root = path.relative(
|
||||
process.cwd(),
|
||||
path.resolve(relativeDistPath ?? "./node_modules/bknd/dist", "static")
|
||||
);
|
||||
let app: App;
|
||||
|
||||
honoServe(
|
||||
{
|
||||
port,
|
||||
hostname,
|
||||
fetch: async (req: Request) => {
|
||||
if (!app) {
|
||||
app = App.create(config);
|
||||
|
||||
app.emgr.on(
|
||||
"app-built",
|
||||
async () => {
|
||||
app.modules.server.get(
|
||||
"/*",
|
||||
serveStatic({
|
||||
root
|
||||
})
|
||||
);
|
||||
app.registerAdminController();
|
||||
},
|
||||
"sync"
|
||||
);
|
||||
|
||||
await app.build();
|
||||
}
|
||||
|
||||
return app.fetch(req);
|
||||
}
|
||||
},
|
||||
(connInfo) => {
|
||||
console.log(`Server is running on http://localhost:${connInfo.port}`);
|
||||
listener?.(connInfo);
|
||||
}
|
||||
);
|
||||
}
|
||||
export * from "./node.adapter";
|
||||
export {
|
||||
StorageLocalAdapter,
|
||||
type LocalAdapterConfig
|
||||
} from "../../media/storage/adapters/StorageLocalAdapter";
|
||||
|
||||
64
app/src/adapter/node/node.adapter.ts
Normal file
64
app/src/adapter/node/node.adapter.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import path from "node:path";
|
||||
import { serve as honoServe } from "@hono/node-server";
|
||||
import { serveStatic } from "@hono/node-server/serve-static";
|
||||
import { App, type CreateAppConfig } from "bknd";
|
||||
|
||||
export type NodeAdapterOptions = CreateAppConfig & {
|
||||
relativeDistPath?: string;
|
||||
port?: number;
|
||||
hostname?: string;
|
||||
listener?: Parameters<typeof honoServe>[1];
|
||||
onBuilt?: (app: App) => Promise<void>;
|
||||
buildOptions?: Parameters<App["build"]>[0];
|
||||
};
|
||||
|
||||
export function serve({
|
||||
relativeDistPath,
|
||||
port = 1337,
|
||||
hostname,
|
||||
listener,
|
||||
onBuilt,
|
||||
buildOptions = {},
|
||||
...config
|
||||
}: NodeAdapterOptions = {}) {
|
||||
const root = path.relative(
|
||||
process.cwd(),
|
||||
path.resolve(relativeDistPath ?? "./node_modules/bknd/dist", "static")
|
||||
);
|
||||
let app: App;
|
||||
|
||||
honoServe(
|
||||
{
|
||||
port,
|
||||
hostname,
|
||||
fetch: async (req: Request) => {
|
||||
if (!app) {
|
||||
app = App.create(config);
|
||||
|
||||
app.emgr.onEvent(
|
||||
App.Events.AppBuiltEvent,
|
||||
async () => {
|
||||
app.modules.server.get(
|
||||
"/*",
|
||||
serveStatic({
|
||||
root
|
||||
})
|
||||
);
|
||||
app.registerAdminController();
|
||||
await onBuilt?.(app);
|
||||
},
|
||||
"sync"
|
||||
);
|
||||
|
||||
await app.build(buildOptions);
|
||||
}
|
||||
|
||||
return app.fetch(req);
|
||||
}
|
||||
},
|
||||
(connInfo) => {
|
||||
console.log(`Server is running on http://localhost:${connInfo.port}`);
|
||||
listener?.(connInfo);
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -8,8 +8,8 @@ function createApp(config: BkndConfig, env: any) {
|
||||
}
|
||||
|
||||
function setAppBuildListener(app: App, config: BkndConfig, html?: string) {
|
||||
app.emgr.on(
|
||||
"app-built",
|
||||
app.emgr.onEvent(
|
||||
App.Events.AppBuiltEvent,
|
||||
async () => {
|
||||
await config.onBuilt?.(app);
|
||||
if (config.setAdminHtml) {
|
||||
|
||||
@@ -220,15 +220,23 @@ export class Authenticator<Strategies extends Record<string, Strategy> = Record<
|
||||
}
|
||||
|
||||
private async getAuthCookie(c: Context): Promise<string | undefined> {
|
||||
const secret = this.config.jwt.secret;
|
||||
try {
|
||||
const secret = this.config.jwt.secret;
|
||||
|
||||
const token = await getSignedCookie(c, secret, "auth");
|
||||
if (typeof token !== "string") {
|
||||
await deleteCookie(c, "auth", this.cookieOptions);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return token;
|
||||
} catch (e: any) {
|
||||
if (e instanceof Error) {
|
||||
console.error("[Error:getAuthCookie]", e.message);
|
||||
}
|
||||
|
||||
const token = await getSignedCookie(c, secret, "auth");
|
||||
if (typeof token !== "string") {
|
||||
await deleteCookie(c, "auth", this.cookieOptions);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return token;
|
||||
}
|
||||
|
||||
async requestCookieRefresh(c: Context) {
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import type { Config } from "@libsql/client/node";
|
||||
import { App, type CreateAppConfig } from "App";
|
||||
import type { BkndConfig } from "adapter";
|
||||
import { StorageLocalAdapter } from "adapter/node";
|
||||
import type { CliCommand } from "cli/types";
|
||||
import { Option } from "commander";
|
||||
import { registries } from "modules/registries";
|
||||
import {
|
||||
PLATFORMS,
|
||||
type Platform,
|
||||
@@ -37,6 +39,12 @@ export const run: CliCommand = (program) => {
|
||||
.action(action);
|
||||
};
|
||||
|
||||
// automatically register local adapter
|
||||
const local = StorageLocalAdapter.prototype.getName();
|
||||
if (!registries.media.has(local)) {
|
||||
registries.media.register(local, StorageLocalAdapter);
|
||||
}
|
||||
|
||||
type MakeAppConfig = {
|
||||
connection?: CreateAppConfig["connection"];
|
||||
server?: { platform?: Platform };
|
||||
@@ -47,8 +55,8 @@ type MakeAppConfig = {
|
||||
async function makeApp(config: MakeAppConfig) {
|
||||
const app = App.create({ connection: config.connection });
|
||||
|
||||
app.emgr.on(
|
||||
"app-built",
|
||||
app.emgr.onEvent(
|
||||
App.Events.AppBuiltEvent,
|
||||
async () => {
|
||||
await attachServeStatic(app, config.server?.platform ?? "node");
|
||||
app.registerAdminController();
|
||||
@@ -68,8 +76,8 @@ export async function makeConfigApp(config: BkndConfig, platform?: Platform) {
|
||||
const appConfig = typeof config.app === "function" ? config.app(process.env) : config.app;
|
||||
const app = App.create(appConfig);
|
||||
|
||||
app.emgr.on(
|
||||
"app-built",
|
||||
app.emgr.onEvent(
|
||||
App.Events.AppBuiltEvent,
|
||||
async () => {
|
||||
await attachServeStatic(app, platform ?? "node");
|
||||
app.registerAdminController();
|
||||
|
||||
@@ -69,7 +69,8 @@ export class SchemaObject<Schema extends TObject> {
|
||||
forceParse: true,
|
||||
skipMark: this.isForceParse()
|
||||
});
|
||||
const updatedConfig = noEmit ? valid : await this.onBeforeUpdate(this._config, valid);
|
||||
// regardless of "noEmit" – this should always be triggered
|
||||
const updatedConfig = await this.onBeforeUpdate(this._config, valid);
|
||||
|
||||
this._value = updatedConfig;
|
||||
this._config = Object.freeze(updatedConfig);
|
||||
|
||||
@@ -1,29 +1,50 @@
|
||||
export type Constructor<T> = new (...args: any[]) => T;
|
||||
export class Registry<Item, Items extends Record<string, object> = Record<string, object>> {
|
||||
|
||||
export type RegisterFn<Item> = (unknown: any) => Item;
|
||||
|
||||
export class Registry<
|
||||
Item,
|
||||
Items extends Record<string, Item> = Record<string, Item>,
|
||||
Fn extends RegisterFn<Item> = RegisterFn<Item>
|
||||
> {
|
||||
private is_set: boolean = false;
|
||||
private items: Items = {} as Items;
|
||||
|
||||
set<Actual extends Record<string, object>>(items: Actual) {
|
||||
constructor(private registerFn?: Fn) {}
|
||||
|
||||
set<Actual extends Record<string, Item>>(items: Actual) {
|
||||
if (this.is_set) {
|
||||
throw new Error("Registry is already set");
|
||||
}
|
||||
// @ts-ignore
|
||||
this.items = items;
|
||||
this.items = items as unknown as Items;
|
||||
this.is_set = true;
|
||||
|
||||
return this as unknown as Registry<Item, Actual>;
|
||||
return this as unknown as Registry<Item, Actual, Fn>;
|
||||
}
|
||||
|
||||
add(name: string, item: Item) {
|
||||
// @ts-ignore
|
||||
this.items[name] = item;
|
||||
this.items[name as keyof Items] = item as Items[keyof Items];
|
||||
return this;
|
||||
}
|
||||
|
||||
register(name: string, specific: Parameters<Fn>[0]) {
|
||||
if (this.registerFn) {
|
||||
const item = this.registerFn(specific);
|
||||
this.items[name as keyof Items] = item as Items[keyof Items];
|
||||
return this;
|
||||
}
|
||||
|
||||
return this.add(name, specific);
|
||||
}
|
||||
|
||||
get<Name extends keyof Items>(name: Name): Items[Name] {
|
||||
return this.items[name];
|
||||
}
|
||||
|
||||
has(name: keyof Items): boolean {
|
||||
return name in this.items;
|
||||
}
|
||||
|
||||
all() {
|
||||
return this.items;
|
||||
}
|
||||
|
||||
@@ -7,5 +7,7 @@ export {
|
||||
type ModuleSchemas
|
||||
} from "modules/ModuleManager";
|
||||
|
||||
export { registries } from "modules/registries";
|
||||
|
||||
export type * from "./adapter";
|
||||
export { Api, type ApiOptions } from "./Api";
|
||||
|
||||
@@ -17,10 +17,6 @@ import {
|
||||
import { type S3AdapterConfig, StorageS3Adapter } from "./storage/adapters/StorageS3Adapter";
|
||||
|
||||
export { StorageS3Adapter, type S3AdapterConfig, StorageCloudinaryAdapter, type CloudinaryConfig };
|
||||
/*export {
|
||||
StorageLocalAdapter,
|
||||
type LocalAdapterConfig
|
||||
} from "./storage/adapters/StorageLocalAdapter";*/
|
||||
|
||||
export * as StorageEvents from "./storage/events";
|
||||
export { type FileUploadedEventData } from "./storage/events";
|
||||
@@ -31,16 +27,12 @@ type ClassThatImplements<T> = Constructor<T> & { prototype: T };
|
||||
export const MediaAdapterRegistry = new Registry<{
|
||||
cls: ClassThatImplements<StorageAdapter>;
|
||||
schema: TObject;
|
||||
}>().set({
|
||||
s3: {
|
||||
cls: StorageS3Adapter,
|
||||
schema: StorageS3Adapter.prototype.getSchema()
|
||||
},
|
||||
cloudinary: {
|
||||
cls: StorageCloudinaryAdapter,
|
||||
schema: StorageCloudinaryAdapter.prototype.getSchema()
|
||||
}
|
||||
});
|
||||
}>((cls: ClassThatImplements<StorageAdapter>) => ({
|
||||
cls,
|
||||
schema: cls.prototype.getSchema() as TObject
|
||||
}))
|
||||
.register("s3", StorageS3Adapter)
|
||||
.register("cloudinary", StorageCloudinaryAdapter);
|
||||
|
||||
export const Adapters = {
|
||||
s3: {
|
||||
|
||||
@@ -1,17 +1,11 @@
|
||||
import { readFile, readdir, stat, unlink, writeFile } from "node:fs/promises";
|
||||
import { type Static, Type, parse } from "core/utils";
|
||||
import type {
|
||||
FileBody,
|
||||
FileListObject,
|
||||
FileMeta,
|
||||
FileUploadPayload,
|
||||
StorageAdapter
|
||||
} from "../../Storage";
|
||||
import type { FileBody, FileListObject, FileMeta, StorageAdapter } from "../../Storage";
|
||||
import { guessMimeType } from "../../mime-types";
|
||||
|
||||
export const localAdapterConfig = Type.Object(
|
||||
{
|
||||
path: Type.String()
|
||||
path: Type.String({ default: "./" })
|
||||
},
|
||||
{ title: "Local" }
|
||||
);
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
import { serveStatic } from "@hono/node-server/serve-static";
|
||||
import { createClient } from "@libsql/client/node";
|
||||
import { App } from "./src";
|
||||
import { App, registries } from "./src";
|
||||
import { LibsqlConnection } from "./src/data";
|
||||
import { StorageLocalAdapter } from "./src/media/storage/adapters/StorageLocalAdapter";
|
||||
import { registries } from "./src/modules/registries";
|
||||
|
||||
registries.media.add("local", {
|
||||
cls: StorageLocalAdapter,
|
||||
schema: StorageLocalAdapter.prototype.getSchema()
|
||||
});
|
||||
registries.media.register("local", StorageLocalAdapter);
|
||||
|
||||
const credentials = {
|
||||
url: import.meta.env.VITE_DB_URL!,
|
||||
@@ -24,8 +20,8 @@ export default {
|
||||
async fetch(request: Request) {
|
||||
const app = App.create({ connection });
|
||||
|
||||
app.emgr.on(
|
||||
"app-built",
|
||||
app.emgr.onEvent(
|
||||
App.Events.AppBuiltEvent,
|
||||
async () => {
|
||||
app.registerAdminController({ forceDev: true });
|
||||
app.module.server.client.get("/assets/*", serveStatic({ root: "./" }));
|
||||
|
||||
Reference in New Issue
Block a user