Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 32 additions & 2 deletions lib/typeorm-core.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,35 @@ import { TYPEORM_MODULE_ID, TYPEORM_MODULE_OPTIONS } from './typeorm.constants';
@Global()
@Module({})
export class TypeOrmCoreModule implements OnApplicationShutdown {
private readonly logger = new Logger('TypeOrmModule');
private static readonly logger = new Logger('TypeOrmModule');

private static validateDataSourceNames(
options: TypeOrmModuleOptions | TypeOrmModuleOptions[],
): void {
const configs = Array.isArray(options) ? options : [options];
const names = new Map<string, number>();

configs.forEach((config) => {
if (!config) {
return;
}
const name = (config as any).name || 'default';
const count = names.get(name) || 0;
names.set(name, count + 1);
});

const duplicates = Array.from(names.entries())
.filter(([_, occurrences]) => occurrences > 1)
.map(([name]) => name);

if (duplicates.length > 0) {
this.logger.warn(
`⚠️ WARNING: Duplicate DataSource names detected: [${duplicates.join(', ')}]. ` +
`Each DataSource should have a unique name to avoid conflicts.` +
`Multiple DataSources with default name will result in unexpected behaviour.`,
);
}
}

constructor(
@Inject(TYPEORM_MODULE_OPTIONS)
Expand All @@ -44,6 +72,7 @@ export class TypeOrmCoreModule implements OnApplicationShutdown {
) {}

static forRoot(options: TypeOrmModuleOptions = {}): DynamicModule {
this.validateDataSourceNames(options);
const typeOrmModuleOptions = {
provide: TYPEORM_MODULE_OPTIONS,
useValue: options,
Expand Down Expand Up @@ -83,6 +112,7 @@ export class TypeOrmCoreModule implements OnApplicationShutdown {
const dataSourceProvider = {
provide: getDataSourceToken(options as DataSourceOptions),
useFactory: async (typeOrmOptions: TypeOrmModuleOptions) => {
this.validateDataSourceNames(typeOrmOptions);
if (options.name) {
return await this.createDataSourceFactory(
{
Expand Down Expand Up @@ -147,7 +177,7 @@ export class TypeOrmCoreModule implements OnApplicationShutdown {
await dataSource.destroy();
}
} catch (e) {
this.logger.error(e?.message);
TypeOrmCoreModule.logger.error(e?.message);
}
}

Expand Down
143 changes: 143 additions & 0 deletions tests/e2e/typeorm-duplicate-datasource-names.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import { TypeOrmCoreModule } from '../../lib/typeorm-core.module';
import { Logger } from '@nestjs/common';

describe('TypeOrmModule - validateDataSourceNames', () => {
let warnSpy: jest.SpyInstance;

beforeEach(() => {
warnSpy = jest
.spyOn((TypeOrmCoreModule as any).logger, 'warn')
.mockImplementation();
});

afterEach(() => {
jest.restoreAllMocks();
});

it('should log a warning when duplicate data source names are provided', () => {
const options = [
{ name: 'default', type: 'sqlite', database: ':memory:' },
{ name: 'secondary', type: 'sqlite', database: ':memory:' },
{ name: 'secondary', type: 'sqlite', database: ':memory:' },
];

(TypeOrmCoreModule as any)['validateDataSourceNames'](options);

expect(warnSpy).toHaveBeenCalledWith(
expect.stringContaining(
'Duplicate DataSource names detected: [secondary]',
),
);
});

it('should not log a warning when all data source names are unique', () => {
const options = [
{ name: 'default', type: 'sqlite', database: ':memory:' },
{ name: 'secondary', type: 'sqlite', database: ':memory:' },
];

(TypeOrmCoreModule as any)['validateDataSourceNames'](options);

expect(warnSpy).not.toHaveBeenCalled();
});

it('should treat missing names as "default" and log warning if repeated', () => {
const options = [
{ type: 'sqlite', database: ':memory:' },
{ type: 'sqlite', database: ':memory:' },
];

(TypeOrmCoreModule as any)['validateDataSourceNames'](options);

expect(warnSpy).toHaveBeenCalledWith(
expect.stringContaining('Duplicate DataSource names detected: [default]'),
);
});

it('should not log warning for a single configuration object', () => {
const options = { name: 'default', type: 'sqlite', database: ':memory:' };

(TypeOrmCoreModule as any)['validateDataSourceNames'](options);

expect(warnSpy).not.toHaveBeenCalled();
});

it('should handle an empty array without throwing or logging', () => {
const options: any[] = [];

expect(() => {
(TypeOrmCoreModule as any)['validateDataSourceNames'](options);
}).not.toThrow();

expect(warnSpy).not.toHaveBeenCalled();
});

it('should handle mixed named and unnamed configurations correctly', () => {
const options = [
{ name: 'primary', type: 'sqlite', database: ':memory:' },
{ type: 'sqlite', database: ':memory:' },
{ type: 'sqlite', database: ':memory:' },
];

(TypeOrmCoreModule as any)['validateDataSourceNames'](options);

expect(warnSpy).toHaveBeenCalledWith(
expect.stringContaining('Duplicate DataSource names detected: [default]'),
);
});

it('should log warning when multiple different duplicates exist', () => {
const options = [
{ name: 'alpha', type: 'sqlite', database: ':memory:' },
{ name: 'alpha', type: 'sqlite', database: ':memory:' },
{ name: 'beta', type: 'sqlite', database: ':memory:' },
{ name: 'beta', type: 'sqlite', database: ':memory:' },
];

(TypeOrmCoreModule as any)['validateDataSourceNames'](options);

expect(warnSpy).toHaveBeenCalledWith(
expect.stringContaining(
'Duplicate DataSource names detected: [alpha, beta]',
),
);
});

it('should handle case-sensitive names as different (no warning)', () => {
const options = [
{ name: 'Main', type: 'sqlite', database: ':memory:' },
{ name: 'main', type: 'sqlite', database: ':memory:' },
];

(TypeOrmCoreModule as any)['validateDataSourceNames'](options);

expect(warnSpy).not.toHaveBeenCalled();
});

it('should not crash if one of the configs is null or undefined', () => {
const options = [
{ name: 'alpha', type: 'sqlite', database: ':memory:' },
null as any,
undefined as any,
{ name: 'beta', type: 'sqlite', database: ':memory:' },
];

expect(() => {
(TypeOrmCoreModule as any)['validateDataSourceNames'](options);
}).not.toThrow();
});

it('should log warning if all names are missing (multiple "default")', () => {
const options = [
{ type: 'sqlite', database: ':memory:' },
{ type: 'sqlite', database: ':memory:' },
{ type: 'sqlite', database: ':memory:' },
];

(TypeOrmCoreModule as any)['validateDataSourceNames'](options);

expect(warnSpy).toHaveBeenCalledWith(
expect.stringContaining('Duplicate DataSource names detected: [default]'),
);
});
});