From 786d1d1bd452f66288bc09debbb90850e21d6d7b Mon Sep 17 00:00:00 2001 From: dswbx Date: Sat, 5 Jul 2025 18:12:55 +0200 Subject: [PATCH] init media adapter refactoring --- app/__test__/modules/AppMedia.spec.ts | 10 +--- app/src/cli/commands/run/run.ts | 6 +-- app/src/core/object/SchemaObject.ts | 2 +- app/src/index.ts | 1 - app/src/media/AppMedia.ts | 55 +++++++++++++++++++-- app/src/media/index.ts | 26 ---------- app/src/media/media-schema.ts | 69 ++++++++++----------------- app/src/modules/ModuleManager.ts | 11 +++-- app/src/modules/registries.ts | 7 --- 9 files changed, 86 insertions(+), 101 deletions(-) delete mode 100644 app/src/modules/registries.ts diff --git a/app/__test__/modules/AppMedia.spec.ts b/app/__test__/modules/AppMedia.spec.ts index 8fc5ffba..95c44190 100644 --- a/app/__test__/modules/AppMedia.spec.ts +++ b/app/__test__/modules/AppMedia.spec.ts @@ -1,22 +1,15 @@ import { describe, expect, test } from "bun:test"; -import { registries } from "../../src"; import { createApp } from "core/test/utils"; import { em, entity, text } from "../../src/data"; import { StorageLocalAdapter } from "adapter/node/storage/StorageLocalAdapter"; import { AppMedia } from "../../src/media/AppMedia"; -import { mediaConfigSchema } from "../../src/media/media-schema"; import { moduleTestSuite } from "./module-test-suite"; describe("AppMedia", () => { - test.only("...", () => { - const media = new AppMedia(); - console.log(media.toJSON()); - }); - moduleTestSuite(AppMedia); test("should allow additional fields", async () => { - registries.media.register("local", StorageLocalAdapter); + //registries.media.register("local", StorageLocalAdapter); const app = createApp({ initialConfig: { @@ -37,6 +30,7 @@ describe("AppMedia", () => { }).toJSON(), }, }); + app.module.media.adapters.set("local", StorageLocalAdapter); await app.build(); diff --git a/app/src/cli/commands/run/run.ts b/app/src/cli/commands/run/run.ts index 0830bc69..a379cf72 100644 --- a/app/src/cli/commands/run/run.ts +++ b/app/src/cli/commands/run/run.ts @@ -5,7 +5,6 @@ import type { CliBkndConfig, CliCommand } from "cli/types"; import { Option } from "commander"; import { config } from "core"; import dotenv from "dotenv"; -import { registries } from "modules/registries"; import c from "picocolors"; import path from "node:path"; import { @@ -58,10 +57,11 @@ export const run: CliCommand = (program) => { }; // automatically register local adapter -const local = StorageLocalAdapter.prototype.getName(); +// @todo: add back +/* const local = StorageLocalAdapter.prototype.getName(); if (!registries.media.has(local)) { registries.media.register(local, StorageLocalAdapter); -} +} */ type MakeAppConfig = { connection?: CreateAppConfig["connection"]; diff --git a/app/src/core/object/SchemaObject.ts b/app/src/core/object/SchemaObject.ts index 3117758c..66ea36fd 100644 --- a/app/src/core/object/SchemaObject.ts +++ b/app/src/core/object/SchemaObject.ts @@ -29,7 +29,7 @@ export class SchemaObject { this._default = _schema.template({}, { withOptional: true }) as any; this._value = parse(_schema, structuredClone(initial ?? {}), { withDefaults: true, - withExtendedDefaults: true, + //withExtendedDefaults: true, forceParse: this.isForceParse(), skipMark: this.isForceParse(), }); diff --git a/app/src/index.ts b/app/src/index.ts index ed12dbb9..6a7550bc 100644 --- a/app/src/index.ts +++ b/app/src/index.ts @@ -34,7 +34,6 @@ export type { ServerEnv } from "modules/Controller"; export type { BkndConfig } from "bknd/adapter"; export * as middlewares from "modules/middlewares"; -export { registries } from "modules/registries"; export type { MediaFieldSchema } from "media/AppMedia"; export type { UserFieldSchema } from "auth/AppAuth"; diff --git a/app/src/media/AppMedia.ts b/app/src/media/AppMedia.ts index aaf31db1..39734d75 100644 --- a/app/src/media/AppMedia.ts +++ b/app/src/media/AppMedia.ts @@ -1,12 +1,15 @@ -import type { AppEntity } from "core"; -import { $console } from "core/utils"; +import type { AppEntity, Constructor } from "core"; +import { $console, objectTransform } from "core/utils"; import type { Entity, EntityManager } from "data"; import { type FileUploadedEventData, Storage, type StorageAdapter, MediaPermissions } from "media"; import { Module } from "modules/Module"; import { type FieldSchema, em, entity } from "../data/prototype"; 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 { 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; declare module "core" { @@ -15,10 +18,12 @@ declare module "core" { media: Media; } } +type ClassThatImplements = Constructor & { prototype: T }; // @todo: current workaround to make it all required export class AppMedia extends Module> { private _storage?: Storage; + adapters: Map> = new Map(); override async build() { if (!this.config.enabled) { @@ -26,6 +31,15 @@ export class AppMedia extends Module> { return; } + // register default adapters + if (!this.adapters.has("s3")) { + this.adapters.set("s3", StorageS3Adapter); + } + + if (!this.adapters.has("cloudinary")) { + this.adapters.set("cloudinary", StorageCloudinaryAdapter); + } + if (!this.config.adapter) { console.info("No storage adapter provided, skip building media."); return; @@ -35,7 +49,11 @@ export class AppMedia extends Module> { let adapter: StorageAdapter; try { 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); this._storage = new Storage(adapter, this.config.storage, this.ctx.emgr); @@ -59,7 +77,34 @@ export class AppMedia extends Module> { } 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() { diff --git a/app/src/media/index.ts b/app/src/media/index.ts index ce3d9b00..c4610c56 100644 --- a/app/src/media/index.ts +++ b/app/src/media/index.ts @@ -1,5 +1,3 @@ -import { type Constructor, Registry } from "core"; - export { guess as guessMimeType } from "./storage/mime-types-tiny"; export { Storage, @@ -15,7 +13,6 @@ import { StorageCloudinaryAdapter, } from "./storage/adapters/cloudinary/StorageCloudinaryAdapter"; import { type S3AdapterConfig, StorageS3Adapter } from "./storage/adapters/s3/StorageS3Adapter"; -import type { s } from "core/object/schema"; export { StorageAdapter }; export { StorageS3Adapter, type S3AdapterConfig, StorageCloudinaryAdapter, type CloudinaryConfig }; @@ -25,27 +22,4 @@ export * as MediaPermissions from "./media-permissions"; export type { FileUploadedEventData } from "./storage/events"; export * from "./utils"; -type ClassThatImplements = Constructor & { prototype: T }; - -export const MediaAdapterRegistry = new Registry<{ - cls: ClassThatImplements; - schema: s.Schema; -}>((cls: ClassThatImplements) => ({ - 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"; diff --git a/app/src/media/media-schema.ts b/app/src/media/media-schema.ts index 6c9bdae0..de467637 100644 --- a/app/src/media/media-schema.ts +++ b/app/src/media/media-schema.ts @@ -1,50 +1,29 @@ -import { objectTransform } from "core/utils"; -import { Adapters } from "media"; -import { registries } from "modules/registries"; import { s } from "core/object/schema"; -export const ADAPTERS = { - ...Adapters, -} as const; - -export const registry = registries.media; - -export function buildMediaSchema() { - const adapterSchemaObject = objectTransform(registry.all(), (adapter, name) => { - return s.strictObject( +export const mediaConfigSchema = s.strictObject( + { + enabled: s.boolean({ default: false }), + basepath: s.string({ default: "/api/media" }), + entity_name: s.string({ default: "media" }), + storage: s.strictObject( { - type: s.literal(name), - config: adapter.schema, + body_max_size: s + .number({ + description: "Max size of the body in bytes. Leave blank for unlimited.", + }) + .optional(), }, - { - title: adapter.schema?.title ?? name, - description: adapter.schema?.description, - }, - ); - }); - - return s.strictObject( - { - enabled: s.boolean({ default: false }), - basepath: s.string({ default: "/api/media" }), - 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(); + { default: {} }, + ), + adapter: s + .strictObject({ + type: s.string(), + config: s.any(), + }) + .optional(), + }, + { + default: {}, + }, +); export type TAppMediaConfig = s.Static; diff --git a/app/src/modules/ModuleManager.ts b/app/src/modules/ModuleManager.ts index fbd342ed..cee1b47a 100644 --- a/app/src/modules/ModuleManager.ts +++ b/app/src/modules/ModuleManager.ts @@ -20,7 +20,7 @@ import { AppMedia } from "../media/AppMedia"; import type { ServerEnv } from "./Controller"; import { Module, type ModuleBuildContext } from "./Module"; import { ModuleHelper } from "./ModuleHelper"; -import { s, mark, stripMark } from "core/object/schema"; +import { s } from "core/object/schema"; export type { ModuleBuildContext }; @@ -166,7 +166,8 @@ export class ModuleManager { if ("version" in options.initial) { const { version, ...initialConfig } = options.initial; this._version = version; - initial = stripMark(initialConfig); + //initial = stripMark(initialConfig); + initial = initialConfig; this._booted_with = "provided"; } else { @@ -487,10 +488,11 @@ export class ModuleManager { // set version and config from fetched this._version = result.version; - if (this.options?.trustFetched === true) { + // @todo: add back? + /* if (this.options?.trustFetched === true) { this.logger.log("trusting fetched config (mark)"); mark(result.json); - } + } */ // if version doesn't match, migrate before building if (this.version() !== CURRENT_VERSION) { @@ -732,7 +734,6 @@ export function getDefaultSchema() { export function getDefaultConfig(): ModuleConfigs { const config = transformObject(MODULES, (module) => { return module.prototype.getSchema().template(); - //return Default(module.prototype.getSchema(), {}); }); return config as any; diff --git a/app/src/modules/registries.ts b/app/src/modules/registries.ts deleted file mode 100644 index fdd29aa0..00000000 --- a/app/src/modules/registries.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { MediaAdapterRegistry } from "media"; - -const registries = { - media: MediaAdapterRegistry, -} as const; - -export { registries };