Good day everyone, faced a trouble while creating my own dynamic module for NestJS & cassandra-driver.
The point is that I have two static methods:
Module code:
import { Module, DynamicModule } from '@nestjs/common';
import { Client, mapping } from 'cassandra-driver';
import { getTableName } from './scylla.decorator';
import Mapper = mapping.Mapper;
const MAPPER_PREFIX = '_MAPPER';
interface RootOptions {
contactPoints: string[];
keyspace: string;
username: string;
password: string;
localDataCenter?: string;
}
@Module({})
export class ScyllaModule {
private static scyllaClient: Client;
static forRoot(options: RootOptions): DynamicModule {
return {
module: ScyllaModule,
providers: [
{
provide: 'SCYLLA_CLIENT',
useValue: this.createClient(options),
},
],
exports: ['SCYLLA_CLIENT'],
};
}
static forFeature(
entities: (new (...args) => any)[],
): DynamicModule {
const newMappers = {};
const providersNames = [];
for (const entity of entities) {
const tableName = getTableName(entity);
if (!tableName) throw new Error('No entity name provided!');
newMappers[tableName + MAPPER_PREFIX] = new Mapper(this.scyllaClient, {
models: { [tableName + 'Model']: { tables: [tableName] } },
});
providersNames.push(tableName + MAPPER_PREFIX);
}
return {
module: ScyllaModule,
providers: Object.entries(newMappers).map(([key, value]) => {
const mapper = value as Mapper;
return {
provide: key,
useValue: mapper.forModel(key.replace(MAPPER_PREFIX, '')),
};
}),
exports: providersNames,
};
}
private static createClient(options: RootOptions): Client {
const { contactPoints, keyspace, username, password, localDataCenter } =
options;
const client = new Client({
contactPoints,
localDataCenter: localDataCenter || 'datacenter1',
keyspace,
credentials: { username, password },
});
client.connect().catch((error) => {
throw new Error(error.message);
});
this.scyllaClient = client;
return client;
}
}
Then, using this in another module like this will not cause any trouble and work as expected.
ScyllaModule.forRoot({
contactPoints: ['localhost'],
keyspace: 'playground',
username: 'cassandra',
password: 'cassandra',
}),
ScyllaModule.forFeature([Test])
For the final i`ve tried to create forRootAsync method with factory, and that's why I created the question.
So, starting with async method:
static async forRootAsync(options: {
useFactory: (...args: any[]) => Promise<RootOptions> | RootOptions;
inject?: any[];
}): Promise<DynamicModule> {
return {
module: ScyllaModule,
providers: [
{
provide: 'SCYLLA_CLIENT',
useFactory: async (...args: any[]) => {
const clientOptions = await options.useFactory(...args);
return this.createClient(clientOptions);
},
inject: options.inject || [],
},
],
exports: ['SCYLLA_CLIENT'],
};
}
And using in another module like that:
imports: [
ScyllaModule.forRootAsync({
useFactory: () => {
const data = {
contactPoints: ['localhost'],
keyspace: 'playground',
username: 'cassandra',
password: 'cassandra',
};
return data;
},
}),
ScyllaModule.forFeature([Test])
],
Causes to
throw new Error('client must be defined');
^
Error: client must be defined
at new Mapper (C:\\Users\\User\\WebstormProjects\\scyllaTest\\node_modules\\cassandra-driver\\lib\\mapping\\mapper.js:68:13)
at Function.forFeature (C:\\Users\\User\\WebstormProjects\\scyllaTest\\src\\scylla\\scylla.module.ts:57:47)
In my opinion, throuble happens on first await in forRootAsync, which prevents forFeature run without waiting for forRootAsync to be done.
Tried a lot of possibilities but still zero result. Browsing the libraries only confused me with its complex implementation.
After a some research i've succeeded at this question.
Here it is, .module
file:
static forRootAsync(options: {
useFactory: (...args: any[]) => Promise<RootOptions> | RootOptions;
inject?: any[];
}): DynamicModule {
return {
module: ScyllaModule,
providers: [
{
provide: 'SCYLLA_CLIENT',
useFactory: async (...args: any[]) => {
const clientOptions = await options.useFactory(...args);
const newClient = this.createClient(clientOptions);
return newClient;
},
inject: options.inject || [],
},
],
exports: ['SCYLLA_CLIENT'],
};
}
Class file can create connections and etc. Mapper error solved too, beacuse useFactory waits to inject now.