fix: add SnakeNamingStrategy for TypeORM to match snake_case DB columns

TypeORM entities use camelCase properties (tenantId, passwordHash) but
database tables use snake_case columns (tenant_id, password_hash). The
naming strategy automatically converts between the two conventions.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
hailin 2026-02-22 00:10:08 -08:00
parent a72cbd3778
commit e0ef15df1e
3 changed files with 31 additions and 0 deletions

View File

@ -2,6 +2,7 @@ import { DynamicModule, MiddlewareConsumer, Module, NestModule } from '@nestjs/c
import { TypeOrmModule } from '@nestjs/typeorm';
import { ConfigService } from '@nestjs/config';
import { TenantContextMiddleware } from './tenant-context.middleware';
import { SnakeNamingStrategy } from './snake-naming.strategy';
@Module({})
export class DatabaseModule implements NestModule {
@ -25,6 +26,7 @@ export class DatabaseModule implements NestModule {
autoLoadEntities: true,
synchronize: config.get('NODE_ENV') === 'development',
logging: config.get('DB_LOGGING', 'false') === 'true',
namingStrategy: new SnakeNamingStrategy(),
}),
}),
],

View File

@ -2,3 +2,4 @@ export * from './tenant-aware.repository';
export * from './database.module';
export * from './tenant-provisioning.service';
export * from './tenant-context.middleware';
export * from './snake-naming.strategy';

View File

@ -0,0 +1,28 @@
import { DefaultNamingStrategy, NamingStrategyInterface } from 'typeorm';
function toSnakeCase(str: string): string {
return str.replace(/([A-Z])/g, '_$1').toLowerCase().replace(/^_/, '');
}
export class SnakeNamingStrategy extends DefaultNamingStrategy implements NamingStrategyInterface {
tableName(className: string, customName: string): string {
return customName || toSnakeCase(className);
}
columnName(propertyName: string, customName: string, embeddedPrefixes: string[]): string {
const prefix = embeddedPrefixes.length ? toSnakeCase(embeddedPrefixes.join('_')) + '_' : '';
return prefix + (customName || toSnakeCase(propertyName));
}
relationName(propertyName: string): string {
return toSnakeCase(propertyName);
}
joinColumnName(relationName: string, referencedColumnName: string): string {
return toSnakeCase(relationName) + '_' + referencedColumnName;
}
joinTableColumnName(tableName: string, propertyName: string, columnName?: string): string {
return toSnakeCase(tableName) + '_' + (columnName || toSnakeCase(propertyName));
}
}