Skip to content

Commit

Permalink
feat: create a conditional module
Browse files Browse the repository at this point in the history
With the new `ConditionalModule` devs can now register
modules based on keys in the `process.env` or have them
registered as blanks modules under the guise of a dyanmic module.
This works for nested modules, multiple imports, and dynamic modules.

Signed-off-by: Jay McDoniel <jmcdo29@gmail.com>
  • Loading branch information
jmcdo29 committed Aug 8, 2023
1 parent 3fe4ea0 commit d98a5f8
Show file tree
Hide file tree
Showing 4 changed files with 160 additions and 0 deletions.
24 changes: 24 additions & 0 deletions lib/conditional.module.ts
@@ -0,0 +1,24 @@
import { ModuleMetadata } from '@nestjs/common';
import { ConfigModule } from './config.module';

export class ConditionalModule {
static async registerWhen(
module: Required<ModuleMetadata>['imports'][number],
condition: string | ((env: NodeJS.ProcessEnv) => boolean),
) {
if (typeof condition === 'string') {
const key = condition;
condition = env => {
return env[key]?.toLowerCase() !== 'false';
};
}
await ConfigModule.envVariablesLoaded;
return condition(process.env)
? {
module: ConditionalModule,
imports: [module],
exports: [module],
}
: { module: ConditionalModule, imports: [] };
}
}
1 change: 1 addition & 0 deletions lib/index.ts
@@ -1,3 +1,4 @@
export * from './conditional.module';
export * from './config.module';
export * from './config.service';
export * from './types';
Expand Down
7 changes: 7 additions & 0 deletions tests/e2e/.env.conditional
@@ -0,0 +1,7 @@
FOO="use it"
FOO_FALSE="false"
FOO_DYNAMIC="yes"
FOO_CUSTOM="yeah!"
BAR="yay"
FOOBAR="do it"
QUU="nested!"
128 changes: 128 additions & 0 deletions tests/e2e/conditional.module.spec.ts
@@ -0,0 +1,128 @@
import { Injectable, Module } from '@nestjs/common';
import { Test } from '@nestjs/testing';
import { ConfigModule, ConditionalModule } from '../../lib';
import { join } from 'path';

@Injectable()
class FooService {}

@Injectable()
class FooDynamicService {}

@Module({
providers: [FooService],
exports: [FooService],
})
class FooModule {
static forRoot() {
return {
module: FooModule,
providers: [FooDynamicService],
exports: [FooDynamicService],
};
}
}

@Injectable()
class BarService {}

@Module({
providers: [BarService],
exports: [BarService],
})
class BarModule {}

@Module({
providers: [
{
provide: 'quu',
useValue: 42,
},
],
exports: ['quu'],
})
class QuuModule {}

@Module({
imports: [ConditionalModule.registerWhen(QuuModule, 'QUU')],
exports: [ConditionalModule],
})
class FooBarModule {}

describe('ConditionalModule', () => {
it('it should work for a regular module', async () => {
const modRef = await Test.createTestingModule({
imports: [
ConfigModule.forRoot({
envFilePath: join(process.cwd(), 'tests', 'e2e', '.env.conditional'),
}),
ConditionalModule.registerWhen(FooModule, 'FOO'),
],
}).compile();
expect(modRef.get(FooService, { strict: false })).toBeDefined();
await modRef.close();
});
it('should work for a dynamic module', async () => {
const modRef = await Test.createTestingModule({
imports: [
ConfigModule.forRoot({
envFilePath: join(process.cwd(), 'tests', 'e2e', '.env.conditional'),
}),
ConditionalModule.registerWhen(FooModule.forRoot(), 'FOO_DYNAMIC'),
],
}).compile();
expect(modRef.get(FooDynamicService, { strict: false })).toBeDefined();
await modRef.close();
});
it('should not register when the value is false', async () => {
const modRef = await Test.createTestingModule({
imports: [
ConfigModule.forRoot({
envFilePath: join(process.cwd(), 'tests', 'e2e', '.env.conditional'),
}),
ConditionalModule.registerWhen(FooModule, 'FOO_FALSE'),
],
}).compile();
expect(() => modRef.get(FooService, { strict: false })).toThrow();
await modRef.close();
});
it('should work for a custom condition', async () => {
const modRef = await Test.createTestingModule({
imports: [
ConfigModule.forRoot({
envFilePath: join(process.cwd(), 'tests', 'e2e', '.env.conditional'),
}),
ConditionalModule.registerWhen(FooModule, env => {
return env.FOO_CUSTOM === 'yeah!';
}),
],
}).compile();
expect(modRef.get(FooService, { strict: false })).toBeDefined();
await modRef.close();
});
it('should handle two conditional modules', async () => {
const modRef = await Test.createTestingModule({
imports: [
ConfigModule.forRoot({
envFilePath: join(process.cwd(), 'tests', 'e2e', '.env.conditional'),
}),
ConditionalModule.registerWhen(FooModule, 'FOO'),
ConditionalModule.registerWhen(BarModule, 'BAR'),
],
}).compile();
expect(modRef.get(FooService, { strict: false })).toBeDefined();
expect(modRef.get(BarService, { strict: false })).toBeDefined();
await modRef.close();
});
it('should handle nested conditional module', async () => {
const modRef = await Test.createTestingModule({
imports: [
ConfigModule.forRoot({
envFilePath: join(process.cwd(), 'tests', 'e2e', '.env.conditional'),
}),
ConditionalModule.registerWhen(FooBarModule, 'FOOBAR'),
],
}).compile();
expect(modRef.get('quu', { strict: false })).toBeDefined();
});
});

0 comments on commit d98a5f8

Please sign in to comment.