mirror of
https://github.com/bknd-io/bknd/
synced 2026-08-03 16:46:00 +00:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7a046fa18e | |||
| 786d1d1bd4 |
@@ -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,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,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();
|
||||||
|
|
||||||
|
|||||||
@@ -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 = {}) {
|
||||||
|
|||||||
@@ -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>(
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ export {
|
|||||||
} from "./bindings";
|
} from "./bindings";
|
||||||
export { constants } from "./config";
|
export { constants } from "./config";
|
||||||
export { StorageR2Adapter } 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,4 +1,4 @@
|
|||||||
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";
|
||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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>(
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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,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) {
|
||||||
|
|||||||
@@ -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(),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|||||||
@@ -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,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 "core/object/schema";
|
|
||||||
|
|
||||||
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";
|
||||||
|
|||||||
@@ -1,50 +1,29 @@
|
|||||||
import { objectTransform } from "core/utils";
|
|
||||||
import { Adapters } from "media";
|
|
||||||
import { registries } from "modules/registries";
|
|
||||||
import { s } from "core/object/schema";
|
import { s } from "core/object/schema";
|
||||||
|
|
||||||
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>;
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -20,7 +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, mark, stripMark } from "core/object/schema";
|
import { s } from "core/object/schema";
|
||||||
|
|
||||||
export type { ModuleBuildContext };
|
export type { ModuleBuildContext };
|
||||||
|
|
||||||
@@ -80,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;
|
||||||
};
|
};
|
||||||
@@ -127,8 +129,16 @@ export class ModuleManagerConfigUpdateEvent<
|
|||||||
}> {
|
}> {
|
||||||
static override slug = "mm-config-update";
|
static override slug = "mm-config-update";
|
||||||
}
|
}
|
||||||
|
export class ModuleManagerModulesCreatedEvent extends ModuleManagerEvent<{
|
||||||
|
modules: Modules;
|
||||||
|
ctx: ModuleBuildContext;
|
||||||
|
}> {
|
||||||
|
static override slug = "mm-modules-created";
|
||||||
|
}
|
||||||
|
|
||||||
export const ModuleManagerEvents = {
|
export const ModuleManagerEvents = {
|
||||||
ModuleManagerConfigUpdateEvent,
|
ModuleManagerConfigUpdateEvent,
|
||||||
|
ModuleManagerModulesCreatedEvent,
|
||||||
};
|
};
|
||||||
|
|
||||||
// @todo: cleanup old diffs on upgrade
|
// @todo: cleanup old diffs on upgrade
|
||||||
@@ -166,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 {
|
||||||
@@ -194,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);
|
||||||
@@ -487,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) {
|
||||||
@@ -732,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;
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
import { MediaAdapterRegistry } from "media";
|
|
||||||
|
|
||||||
const registries = {
|
|
||||||
media: MediaAdapterRegistry,
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
export { registries };
|
|
||||||
@@ -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 (!isTypeSchema(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} />;
|
||||||
|
|||||||
+1
-1
@@ -49,5 +49,5 @@
|
|||||||
"__test__",
|
"__test__",
|
||||||
"e2e/**/*.ts"
|
"e2e/**/*.ts"
|
||||||
],
|
],
|
||||||
"exclude": ["node_modules", "dist", "dist/types", "**/*.d.ts"]
|
"exclude": ["node_modules", "dist", "dist/types", "**/*.d.ts", "**/dist/**"]
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-5
@@ -1,8 +1,8 @@
|
|||||||
import { readFile } from "node:fs/promises";
|
import { readFile } from "node:fs/promises";
|
||||||
import { serveStatic } from "@hono/node-server/serve-static";
|
import { serveStatic } from "@hono/node-server/serve-static";
|
||||||
import { showRoutes } from "hono/dev";
|
import { showRoutes } from "hono/dev";
|
||||||
import { App, registries } from "./src";
|
import { App } from "./src/App";
|
||||||
import { StorageLocalAdapter } from "./src/adapter/node";
|
import { StorageLocalAdapter } from "./src/adapter/node/storage/StorageLocalAdapter";
|
||||||
import type { Connection } from "./src/data/connection/Connection";
|
import type { Connection } from "./src/data/connection/Connection";
|
||||||
import { __bknd } from "modules/ModuleManager";
|
import { __bknd } from "modules/ModuleManager";
|
||||||
import { nodeSqlite } from "./src/adapter/node/connection/NodeSqliteConnection";
|
import { nodeSqlite } from "./src/adapter/node/connection/NodeSqliteConnection";
|
||||||
@@ -10,8 +10,6 @@ import { libsql } from "./src/data/connection/sqlite/libsql/LibsqlConnection";
|
|||||||
import { $console } from "core/utils";
|
import { $console } from "core/utils";
|
||||||
import { createClient } from "@libsql/client";
|
import { createClient } from "@libsql/client";
|
||||||
|
|
||||||
registries.media.register("local", StorageLocalAdapter);
|
|
||||||
|
|
||||||
const example = import.meta.env.VITE_EXAMPLE;
|
const example = import.meta.env.VITE_EXAMPLE;
|
||||||
|
|
||||||
let connection: Connection;
|
let connection: Connection;
|
||||||
@@ -70,7 +68,7 @@ if (import.meta.env.VITE_DB_LIBSQL_URL) {
|
|||||||
} */
|
} */
|
||||||
|
|
||||||
let app: App;
|
let app: App;
|
||||||
const recreate = import.meta.env.VITE_APP_FRESH === "1";
|
const recreate = true; //import.meta.env.VITE_APP_FRESH === "1";
|
||||||
const debugRerenders = import.meta.env.VITE_DEBUG_RERENDERS === "1";
|
const debugRerenders = import.meta.env.VITE_DEBUG_RERENDERS === "1";
|
||||||
let firstStart = true;
|
let firstStart = true;
|
||||||
export default {
|
export default {
|
||||||
@@ -78,6 +76,13 @@ export default {
|
|||||||
if (!app || recreate) {
|
if (!app || recreate) {
|
||||||
app = App.create({
|
app = App.create({
|
||||||
connection,
|
connection,
|
||||||
|
options: {
|
||||||
|
manager: {
|
||||||
|
onModulesCreated: (modules, ctx) => {
|
||||||
|
modules.media.adapters.set("local", StorageLocalAdapter);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
app.emgr.onEvent(
|
app.emgr.onEvent(
|
||||||
App.Events.AppBuiltEvent,
|
App.Events.AppBuiltEvent,
|
||||||
|
|||||||
@@ -3,9 +3,6 @@ import { registerLocalMediaAdapter } from "bknd/adapter/node";
|
|||||||
import { boolean, em, entity, text } from "bknd/data";
|
import { boolean, em, entity, text } from "bknd/data";
|
||||||
import { secureRandomString } from "bknd/utils";
|
import { secureRandomString } from "bknd/utils";
|
||||||
|
|
||||||
// since we're running in node, we can register the local media adapter
|
|
||||||
const local = registerLocalMediaAdapter();
|
|
||||||
|
|
||||||
// the em() function makes it easy to create an initial schema
|
// the em() function makes it easy to create an initial schema
|
||||||
const schema = em({
|
const schema = em({
|
||||||
todos: entity("todos", {
|
todos: entity("todos", {
|
||||||
@@ -41,11 +38,17 @@ export default {
|
|||||||
// ... and media
|
// ... and media
|
||||||
media: {
|
media: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
adapter: local({
|
adapter: {
|
||||||
path: "./public/uploads",
|
type: "local",
|
||||||
}),
|
config: {
|
||||||
|
path: "./public/uploads",
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
beforeBuild: (app) => {
|
||||||
|
registerLocalMediaAdapter(app);
|
||||||
|
},
|
||||||
options: {
|
options: {
|
||||||
// the seed option is only executed if the database was empty
|
// the seed option is only executed if the database was empty
|
||||||
seed: async (ctx) => {
|
seed: async (ctx) => {
|
||||||
|
|||||||
@@ -3,16 +3,6 @@ import { boolean, em, entity, text } from "bknd/data";
|
|||||||
import { registerLocalMediaAdapter } from "bknd/adapter/node";
|
import { registerLocalMediaAdapter } from "bknd/adapter/node";
|
||||||
import { secureRandomString } from "bknd/utils";
|
import { secureRandomString } from "bknd/utils";
|
||||||
|
|
||||||
// The local media adapter works well in development, and server based
|
|
||||||
// deployments. However, on vercel or any other serverless deployments,
|
|
||||||
// you shouldn't use a filesystem based media adapter.
|
|
||||||
//
|
|
||||||
// Additionally, if you run the bknd api on the "edge" runtime,
|
|
||||||
// this would not work as well.
|
|
||||||
//
|
|
||||||
// For production, it is recommended to uncomment the line below.
|
|
||||||
const local = registerLocalMediaAdapter();
|
|
||||||
|
|
||||||
const schema = em({
|
const schema = em({
|
||||||
todos: entity("todos", {
|
todos: entity("todos", {
|
||||||
title: text(),
|
title: text(),
|
||||||
@@ -50,11 +40,23 @@ export default {
|
|||||||
// ... and media
|
// ... and media
|
||||||
media: {
|
media: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
adapter: local({
|
adapter: {
|
||||||
path: "./public/uploads",
|
type: "local",
|
||||||
}),
|
config: {
|
||||||
|
path: "./public/uploads",
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
beforeBuild: (app) => {
|
||||||
|
// The local media adapter works well in development, and server based
|
||||||
|
// deployments. However, on vercel or any other serverless deployments,
|
||||||
|
// you shouldn't use a filesystem based media adapter.
|
||||||
|
//
|
||||||
|
// Additionally, if you run the bknd api on the "edge" runtime,
|
||||||
|
// this would not work as well.
|
||||||
|
registerLocalMediaAdapter(app);
|
||||||
|
},
|
||||||
options: {
|
options: {
|
||||||
// the seed option is only executed if the database was empty
|
// the seed option is only executed if the database was empty
|
||||||
seed: async (ctx) => {
|
seed: async (ctx) => {
|
||||||
|
|||||||
@@ -3,9 +3,6 @@ import type { ReactRouterBkndConfig } from "bknd/adapter/react-router";
|
|||||||
import { boolean, em, entity, text } from "bknd/data";
|
import { boolean, em, entity, text } from "bknd/data";
|
||||||
import { secureRandomString } from "bknd/utils";
|
import { secureRandomString } from "bknd/utils";
|
||||||
|
|
||||||
// since we're running in node, we can register the local media adapter
|
|
||||||
const local = registerLocalMediaAdapter();
|
|
||||||
|
|
||||||
const schema = em({
|
const schema = em({
|
||||||
todos: entity("todos", {
|
todos: entity("todos", {
|
||||||
title: text(),
|
title: text(),
|
||||||
@@ -40,11 +37,18 @@ export default {
|
|||||||
// ... and media
|
// ... and media
|
||||||
media: {
|
media: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
adapter: local({
|
adapter: {
|
||||||
path: "./public/uploads",
|
type: "local",
|
||||||
}),
|
config: {
|
||||||
|
path: "./public/uploads",
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
beforeBuild: (app) => {
|
||||||
|
// since we're running in node, we can register the local media adapter
|
||||||
|
registerLocalMediaAdapter(app);
|
||||||
|
},
|
||||||
options: {
|
options: {
|
||||||
// the seed option is only executed if the database was empty
|
// the seed option is only executed if the database was empty
|
||||||
seed: async (ctx) => {
|
seed: async (ctx) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user