I am currently working on a personal project to provide easy access to different storage providers through a NestJS package. I want the package to be fully type-safe but I am experiencing difficulty achieving this.
This is an abstraction of the current code.
// local.driver.ts
interface LocalOptions {
path: string;
}
export class LocalDriver {
constructor(private options: LocalOptions) {}
get() {}
}
// s3.driver.ts
interface S3Options {
key: string;
secret: string;
region: string;
bucket: string;
}
export class S3Driver {
constructor(private options: S3Options) {}
get() {}
}
// storage.service.ts
const drivers = {
local: LocalDriver,
s3: S3Driver,
};
type DriverTypes = keyof typeof drivers;
type Drivers = typeof drivers[DriverTypes];
type Disk = {
[Driver in DriverTypes]: {
type: Driver;
properties: ConstructorParameters<typeof drivers[Driver]>[0];
};
}[DriverTypes];
export type StorageOptions = {
disks: {
[disk: string]: Disk;
};
};
@Injectable()
export class StorageService {
private static diskDrivers: { [disk: string]: Drivers };
private static options: StorageOptions;
constructor(@Inject(STORAGE_OPTIONS) options: StorageOptions) {
StorageService.options = options;
}
static disk(disk: keyof typeof StorageService.options.disks) {
return StorageService.getDriver(disk);
}
private static getDriver(
disk: keyof typeof StorageService.options.disks,
): Drivers {
const diskDriver = StorageService.diskDrivers[disk];
if (diskDriver) return diskDriver;
const driver = StorageService.newDriver(disk);
StorageService.diskDrivers[disk] = driver;
return driver;
}
private static newDriver(
disk: keyof typeof StorageService.options.disks,
): Drivers {
const driverConfig = StorageService.options.disks[disk];
const Driver = drivers[driverConfig.type];
return new Driver(driverConfig.properties);
// ^ Argument of type 'LocalOptions | S3Options' is not assignable to parameter of type 'LocalOptions & S3Options'.
}
}
This is how I register the package in NestJS. When using a specific type, only its options are allowed and will be suggested. E.g. using type s3
, only the S3 options are allowed in the IDE (this is currently working).
// app.module.ts
@Module({
imports: [
StorageModule.register({
disks: {
invoices: {
type: 's3',
properties: {
region: process.env.AWS_REGION,
key: process.env.AWS_KEY,
secret: process.env.AWS_SECRET,
bucket: 'invoices',
},
},
images: {
type: 'local',
properties: {
path: '/images',
},
},
},
}),
],
})
export class AppModule {}
I now want to use the disks but I'm not receiving any suggestions, warnings or errors.
StorageService.disk('').get();
// ^ This should suggest and only allow `invoices` and `images`.
Is there a way to achieve this functionality?