import { type DynamicModule, Module, type Type } from "@nestjs/common"; import { DiskFileStorage } from "./disk-file-storage"; import { FILE_STORAGE } from "./file-storage.interface"; import { R2FileStorage, type R2FileStorageConfig } from "./r2-file-storage"; type DiskConfig = { driver: "disk"; baseDir: string }; type R2Config = { driver: "r2" } & R2FileStorageConfig; export type FileStorageModuleConfig = DiskConfig | R2Config; /** * Implement this interface to provide FileStorage configuration * via NestJS DI. Register it with `FileStorageModule.forRootAsync()`. */ export interface FileStorageConfigProvider { createFileStorageConfig(): FileStorageModuleConfig; } export const FILE_STORAGE_CONFIG_PROVIDER = Symbol("FILE_STORAGE_CONFIG_PROVIDER"); @Module({}) export class FileStorageModule { /** * Static configuration — pass the config directly. */ static forRoot(config: FileStorageModuleConfig): DynamicModule { return { module: FileStorageModule, providers: [ { provide: FILE_STORAGE, useFactory: () => createStorage(config), }, ], exports: [FILE_STORAGE], global: true, }; } /** * Async configuration — provide a class that implements * `FileStorageConfigProvider`. The class can inject ConfigService * or any other NestJS provider. */ static forRootAsync(options: { imports?: Type[]; useClass: Type; }): DynamicModule { return { module: FileStorageModule, imports: options.imports ?? [], providers: [ { provide: FILE_STORAGE_CONFIG_PROVIDER, useClass: options.useClass, }, { provide: FILE_STORAGE, useFactory: (provider: FileStorageConfigProvider) => createStorage(provider.createFileStorageConfig()), inject: [FILE_STORAGE_CONFIG_PROVIDER], }, ], exports: [FILE_STORAGE], global: true, }; } } const createStorage = (config: FileStorageModuleConfig) => config.driver === "r2" ? new R2FileStorage(config) : new DiskFileStorage(config.baseDir);