racing typeorm

This commit is contained in:
2025-12-29 00:24:56 +01:00
parent 2f6657f56d
commit 9e17d0752a
55 changed files with 3528 additions and 22 deletions

View File

@@ -0,0 +1,201 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule, getDataSourceToken } from '@nestjs/typeorm';
import type { DataSource } from 'typeorm';
import { LoggingModule } from '../../domain/logging/LoggingModule';
import {
DRIVER_REPOSITORY_TOKEN,
GAME_REPOSITORY_TOKEN,
LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN,
LEAGUE_REPOSITORY_TOKEN,
LEAGUE_SCORING_CONFIG_REPOSITORY_TOKEN,
LEAGUE_WALLET_REPOSITORY_TOKEN,
PENALTY_REPOSITORY_TOKEN,
PROTEST_REPOSITORY_TOKEN,
RACE_REGISTRATION_REPOSITORY_TOKEN,
RACE_REPOSITORY_TOKEN,
RESULT_REPOSITORY_TOKEN,
SEASON_REPOSITORY_TOKEN,
SEASON_SPONSORSHIP_REPOSITORY_TOKEN,
SPONSOR_REPOSITORY_TOKEN,
SPONSORSHIP_PRICING_REPOSITORY_TOKEN,
SPONSORSHIP_REQUEST_REPOSITORY_TOKEN,
STANDING_REPOSITORY_TOKEN,
TEAM_MEMBERSHIP_REPOSITORY_TOKEN,
TEAM_REPOSITORY_TOKEN,
TRANSACTION_REPOSITORY_TOKEN,
} from '../inmemory/InMemoryRacingPersistenceModule';
import { LeagueOrmEntity } from '@adapters/racing/persistence/typeorm/entities/LeagueOrmEntity';
import { LeagueScoringConfigOrmEntity } from '@adapters/racing/persistence/typeorm/entities/LeagueScoringConfigOrmEntity';
import { RaceOrmEntity } from '@adapters/racing/persistence/typeorm/entities/RaceOrmEntity';
import { SeasonOrmEntity } from '@adapters/racing/persistence/typeorm/entities/SeasonOrmEntity';
import { TypeOrmLeagueRepository } from '@adapters/racing/persistence/typeorm/repositories/TypeOrmLeagueRepository';
import { TypeOrmLeagueScoringConfigRepository } from '@adapters/racing/persistence/typeorm/repositories/TypeOrmLeagueScoringConfigRepository';
import { TypeOrmRaceRepository } from '@adapters/racing/persistence/typeorm/repositories/TypeOrmRaceRepository';
import { TypeOrmSeasonRepository } from '@adapters/racing/persistence/typeorm/repositories/TypeOrmSeasonRepository';
import { LeagueOrmMapper } from '@adapters/racing/persistence/typeorm/mappers/LeagueOrmMapper';
import { RaceOrmMapper } from '@adapters/racing/persistence/typeorm/mappers/RaceOrmMapper';
import { SeasonOrmMapper } from '@adapters/racing/persistence/typeorm/mappers/SeasonOrmMapper';
import { PointsTableJsonMapper } from '@adapters/racing/persistence/typeorm/mappers/PointsTableJsonMapper';
import { ChampionshipConfigJsonMapper } from '@adapters/racing/persistence/typeorm/mappers/ChampionshipConfigJsonMapper';
import { LeagueScoringConfigOrmMapper } from '@adapters/racing/persistence/typeorm/mappers/LeagueScoringConfigOrmMapper';
function makePlaceholder(token: string): unknown {
return Object.freeze({
__token: token,
__kind: 'postgres-placeholder',
__notImplemented(): never {
throw new Error(`[PostgresRacingPersistenceModule] Placeholder provider "${token}" is not implemented yet`);
},
});
}
const typeOrmFeatureImports = [
TypeOrmModule.forFeature([LeagueOrmEntity, SeasonOrmEntity, RaceOrmEntity, LeagueScoringConfigOrmEntity]),
];
@Module({
imports: [LoggingModule, ...typeOrmFeatureImports],
providers: [
{
provide: DRIVER_REPOSITORY_TOKEN,
useFactory: () => makePlaceholder(DRIVER_REPOSITORY_TOKEN),
inject: ['Logger'],
},
{
provide: LEAGUE_REPOSITORY_TOKEN,
useFactory: (dataSource: DataSource) => {
const leagueMapper = new LeagueOrmMapper();
return new TypeOrmLeagueRepository(dataSource, leagueMapper);
},
inject: [getDataSourceToken()],
},
{
provide: RACE_REPOSITORY_TOKEN,
useFactory: (dataSource: DataSource) => {
const raceMapper = new RaceOrmMapper();
return new TypeOrmRaceRepository(dataSource, raceMapper);
},
inject: [getDataSourceToken()],
},
{
provide: RESULT_REPOSITORY_TOKEN,
useFactory: () => makePlaceholder(RESULT_REPOSITORY_TOKEN),
inject: ['Logger'],
},
{
provide: STANDING_REPOSITORY_TOKEN,
useFactory: () => makePlaceholder(STANDING_REPOSITORY_TOKEN),
inject: ['Logger'],
},
{
provide: LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN,
useFactory: () => makePlaceholder(LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN),
inject: ['Logger'],
},
{
provide: RACE_REGISTRATION_REPOSITORY_TOKEN,
useFactory: () => makePlaceholder(RACE_REGISTRATION_REPOSITORY_TOKEN),
inject: ['Logger'],
},
{
provide: TEAM_REPOSITORY_TOKEN,
useFactory: () => makePlaceholder(TEAM_REPOSITORY_TOKEN),
inject: ['Logger'],
},
{
provide: TEAM_MEMBERSHIP_REPOSITORY_TOKEN,
useFactory: () => makePlaceholder(TEAM_MEMBERSHIP_REPOSITORY_TOKEN),
inject: ['Logger'],
},
{
provide: PENALTY_REPOSITORY_TOKEN,
useFactory: () => makePlaceholder(PENALTY_REPOSITORY_TOKEN),
inject: ['Logger'],
},
{
provide: PROTEST_REPOSITORY_TOKEN,
useFactory: () => makePlaceholder(PROTEST_REPOSITORY_TOKEN),
inject: ['Logger'],
},
{
provide: SEASON_REPOSITORY_TOKEN,
useFactory: (dataSource: DataSource) => {
const seasonMapper = new SeasonOrmMapper();
return new TypeOrmSeasonRepository(dataSource, seasonMapper);
},
inject: [getDataSourceToken()],
},
{
provide: SEASON_SPONSORSHIP_REPOSITORY_TOKEN,
useFactory: () => makePlaceholder(SEASON_SPONSORSHIP_REPOSITORY_TOKEN),
inject: ['Logger'],
},
{
provide: LEAGUE_SCORING_CONFIG_REPOSITORY_TOKEN,
useFactory: (dataSource: DataSource) => {
const pointsTableMapper = new PointsTableJsonMapper();
const championshipMapper = new ChampionshipConfigJsonMapper(pointsTableMapper);
const scoringConfigMapper = new LeagueScoringConfigOrmMapper(championshipMapper);
return new TypeOrmLeagueScoringConfigRepository(dataSource, scoringConfigMapper);
},
inject: [getDataSourceToken()],
},
{
provide: GAME_REPOSITORY_TOKEN,
useFactory: () => makePlaceholder(GAME_REPOSITORY_TOKEN),
inject: ['Logger'],
},
{
provide: LEAGUE_WALLET_REPOSITORY_TOKEN,
useFactory: () => makePlaceholder(LEAGUE_WALLET_REPOSITORY_TOKEN),
inject: ['Logger'],
},
{
provide: TRANSACTION_REPOSITORY_TOKEN,
useFactory: () => makePlaceholder(TRANSACTION_REPOSITORY_TOKEN),
inject: ['Logger'],
},
{
provide: SPONSOR_REPOSITORY_TOKEN,
useFactory: () => makePlaceholder(SPONSOR_REPOSITORY_TOKEN),
inject: ['Logger'],
},
{
provide: SPONSORSHIP_PRICING_REPOSITORY_TOKEN,
useFactory: () => makePlaceholder(SPONSORSHIP_PRICING_REPOSITORY_TOKEN),
inject: ['Logger'],
},
{
provide: SPONSORSHIP_REQUEST_REPOSITORY_TOKEN,
useFactory: () => makePlaceholder(SPONSORSHIP_REQUEST_REPOSITORY_TOKEN),
inject: ['Logger'],
},
],
exports: [
DRIVER_REPOSITORY_TOKEN,
LEAGUE_REPOSITORY_TOKEN,
RACE_REPOSITORY_TOKEN,
RESULT_REPOSITORY_TOKEN,
STANDING_REPOSITORY_TOKEN,
LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN,
RACE_REGISTRATION_REPOSITORY_TOKEN,
TEAM_REPOSITORY_TOKEN,
TEAM_MEMBERSHIP_REPOSITORY_TOKEN,
PENALTY_REPOSITORY_TOKEN,
PROTEST_REPOSITORY_TOKEN,
SEASON_REPOSITORY_TOKEN,
SEASON_SPONSORSHIP_REPOSITORY_TOKEN,
LEAGUE_SCORING_CONFIG_REPOSITORY_TOKEN,
GAME_REPOSITORY_TOKEN,
LEAGUE_WALLET_REPOSITORY_TOKEN,
TRANSACTION_REPOSITORY_TOKEN,
SPONSOR_REPOSITORY_TOKEN,
SPONSORSHIP_PRICING_REPOSITORY_TOKEN,
SPONSORSHIP_REQUEST_REPOSITORY_TOKEN,
],
})
export class PostgresRacingPersistenceModule {}

View File

@@ -0,0 +1,158 @@
import 'reflect-metadata';
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
import { DataSource } from 'typeorm';
import { League } from '@core/racing/domain/entities/League';
import { LeagueScoringConfig } from '@core/racing/domain/entities/LeagueScoringConfig';
import { Race } from '@core/racing/domain/entities/Race';
import { Season } from '@core/racing/domain/entities/season/Season';
import { PointsTable } from '@core/racing/domain/value-objects/PointsTable';
import type { ChampionshipConfig } from '@core/racing/domain/types/ChampionshipConfig';
import type { SessionType } from '@core/racing/domain/types/SessionType';
import { LeagueOrmEntity } from '../../../../../../adapters/racing/persistence/typeorm/entities/LeagueOrmEntity';
import { LeagueScoringConfigOrmEntity } from '../../../../../../adapters/racing/persistence/typeorm/entities/LeagueScoringConfigOrmEntity';
import { RaceOrmEntity } from '../../../../../../adapters/racing/persistence/typeorm/entities/RaceOrmEntity';
import { SeasonOrmEntity } from '../../../../../../adapters/racing/persistence/typeorm/entities/SeasonOrmEntity';
import { TypeOrmLeagueRepository } from '../../../../../../adapters/racing/persistence/typeorm/repositories/TypeOrmLeagueRepository';
import { TypeOrmLeagueScoringConfigRepository } from '../../../../../../adapters/racing/persistence/typeorm/repositories/TypeOrmLeagueScoringConfigRepository';
import { TypeOrmRaceRepository } from '../../../../../../adapters/racing/persistence/typeorm/repositories/TypeOrmRaceRepository';
import { TypeOrmSeasonRepository } from '../../../../../../adapters/racing/persistence/typeorm/repositories/TypeOrmSeasonRepository';
import { LeagueOrmMapper } from '../../../../../../adapters/racing/persistence/typeorm/mappers/LeagueOrmMapper';
import { RaceOrmMapper } from '../../../../../../adapters/racing/persistence/typeorm/mappers/RaceOrmMapper';
import { SeasonOrmMapper } from '../../../../../../adapters/racing/persistence/typeorm/mappers/SeasonOrmMapper';
import { PointsTableJsonMapper } from '../../../../../../adapters/racing/persistence/typeorm/mappers/PointsTableJsonMapper';
import { ChampionshipConfigJsonMapper } from '../../../../../../adapters/racing/persistence/typeorm/mappers/ChampionshipConfigJsonMapper';
import { LeagueScoringConfigOrmMapper } from '../../../../../../adapters/racing/persistence/typeorm/mappers/LeagueScoringConfigOrmMapper';
const databaseUrl = process.env.DATABASE_URL;
const describeIfDatabase = databaseUrl ? describe : describe.skip;
describeIfDatabase('TypeORM Racing repositories (postgres slice)', () => {
let dataSource: DataSource;
beforeAll(async () => {
if (!databaseUrl) {
throw new Error('DATABASE_URL is required to run postgres integration tests');
}
dataSource = new DataSource({
type: 'postgres',
url: databaseUrl,
entities: [LeagueOrmEntity, SeasonOrmEntity, RaceOrmEntity, LeagueScoringConfigOrmEntity],
synchronize: true,
});
await dataSource.initialize();
});
afterAll(async () => {
if (dataSource?.isInitialized) {
await dataSource.destroy();
}
});
it('supports: create league + create season + save scoring config + fetch schedule', async () => {
const leagueRepo = new TypeOrmLeagueRepository(dataSource, new LeagueOrmMapper());
const seasonRepo = new TypeOrmSeasonRepository(dataSource, new SeasonOrmMapper());
const raceRepo = new TypeOrmRaceRepository(dataSource, new RaceOrmMapper());
const pointsTableMapper = new PointsTableJsonMapper();
const championshipMapper = new ChampionshipConfigJsonMapper(pointsTableMapper);
const scoringConfigMapper = new LeagueScoringConfigOrmMapper(championshipMapper);
const scoringRepo = new TypeOrmLeagueScoringConfigRepository(dataSource, scoringConfigMapper);
const league = League.create({
id: 'league-it-1',
name: 'Integration League',
description: 'For integration testing',
ownerId: 'driver-it-1',
settings: { pointsSystem: 'custom', visibility: 'unranked', maxDrivers: 32 },
participantCount: 0,
});
await leagueRepo.create(league);
const season = Season.create({
id: 'season-it-1',
leagueId: league.id.toString(),
gameId: 'iracing',
name: 'Integration Season',
year: 2025,
order: 1,
status: 'active',
startDate: new Date('2025-01-01T00:00:00.000Z'),
endDate: new Date('2025-12-31T00:00:00.000Z'),
schedulePublished: false,
});
await seasonRepo.create(season);
const pointsTableBySessionType: Record<SessionType, PointsTable> = {
practice: new PointsTable({}),
qualifying: new PointsTable({}),
q1: new PointsTable({}),
q2: new PointsTable({}),
q3: new PointsTable({}),
sprint: new PointsTable({}),
main: new PointsTable({ 1: 25 }),
timeTrial: new PointsTable({}),
};
const bonusRulesBySessionType = {
practice: [],
qualifying: [],
q1: [],
q2: [],
q3: [],
sprint: [],
main: [],
timeTrial: [],
};
const championship: ChampionshipConfig = {
id: 'champ-it-1',
name: 'Driver Championship',
type: 'driver',
sessionTypes: ['main' as SessionType],
pointsTableBySessionType,
bonusRulesBySessionType,
dropScorePolicy: { strategy: 'none' },
};
const scoring = LeagueScoringConfig.create({
id: 'lsc-it-1',
seasonId: season.id,
scoringPresetId: 'club-default',
championships: [championship],
});
await scoringRepo.save(scoring);
const race = Race.create({
id: 'race-it-1',
leagueId: league.id.toString(),
scheduledAt: new Date('2025-03-01T12:00:00.000Z'),
track: 'Spa',
car: 'GT3',
status: 'scheduled',
});
await raceRepo.create(race);
const persistedLeague = await leagueRepo.findById(league.id.toString());
expect(persistedLeague?.name.toString()).toBe('Integration League');
const seasons = await seasonRepo.findByLeagueId(league.id.toString());
expect(seasons.map((s: Season) => s.id)).toContain('season-it-1');
const races = await raceRepo.findByLeagueId(league.id.toString());
expect(races.map((r: Race) => r.id)).toContain('race-it-1');
const persistedScoring = await scoringRepo.findBySeasonId(season.id);
expect(persistedScoring?.id.toString()).toBe('lsc-it-1');
});
});

View File

@@ -0,0 +1,166 @@
import { describe, expect, it } from 'vitest';
import { League } from '@core/racing/domain/entities/League';
import { LeagueScoringConfig } from '@core/racing/domain/entities/LeagueScoringConfig';
import { Race } from '@core/racing/domain/entities/Race';
import { Season } from '@core/racing/domain/entities/season/Season';
import { PointsTable } from '@core/racing/domain/value-objects/PointsTable';
import { SessionType as RaceSessionType } from '@core/racing/domain/value-objects/SessionType';
import type { ChampionshipConfig } from '@core/racing/domain/types/ChampionshipConfig';
import type { SessionType } from '@core/racing/domain/types/SessionType';
import { ChampionshipConfigJsonMapper } from '@adapters/racing/persistence/typeorm/mappers/ChampionshipConfigJsonMapper';
import { LeagueOrmMapper } from '@adapters/racing/persistence/typeorm/mappers/LeagueOrmMapper';
import { LeagueScoringConfigOrmMapper } from '@adapters/racing/persistence/typeorm/mappers/LeagueScoringConfigOrmMapper';
import { PointsTableJsonMapper } from '@adapters/racing/persistence/typeorm/mappers/PointsTableJsonMapper';
import { RaceOrmMapper } from '@adapters/racing/persistence/typeorm/mappers/RaceOrmMapper';
import { SeasonOrmMapper } from '@adapters/racing/persistence/typeorm/mappers/SeasonOrmMapper';
describe('RacingOrmMappers', () => {
it('maps League domain <-> orm', () => {
const league = League.create({
id: 'league-1',
name: 'My League',
description: 'A description',
ownerId: 'driver-1',
settings: {
pointsSystem: 'custom',
maxDrivers: 48,
visibility: 'unranked',
},
socialLinks: {
discordUrl: 'https://discord.gg/example',
},
participantCount: 12,
createdAt: new Date('2025-01-01T00:00:00.000Z'),
});
const mapper = new LeagueOrmMapper();
const orm = mapper.toOrmEntity(league);
const rehydrated = mapper.toDomain(orm);
expect(rehydrated.id.toString()).toBe('league-1');
expect(rehydrated.name.toString()).toBe('My League');
expect(rehydrated.description.toString()).toBe('A description');
expect(rehydrated.ownerId.toString()).toBe('driver-1');
expect(rehydrated.settings.maxDrivers).toBe(48);
expect(rehydrated.settings.visibility).toBe('unranked');
expect(rehydrated.getParticipantCount()).toBe(12);
expect(rehydrated.socialLinks?.discordUrl).toBe('https://discord.gg/example');
});
it('maps Season domain <-> orm', () => {
const season = Season.create({
id: 'season-1',
leagueId: 'league-1',
gameId: 'iracing',
name: 'Season 1',
year: 2025,
order: 1,
status: 'active',
startDate: new Date('2025-01-01T00:00:00.000Z'),
endDate: new Date('2025-02-01T00:00:00.000Z'),
schedulePublished: true,
participantCount: 7,
maxDrivers: 32,
});
const mapper = new SeasonOrmMapper();
const orm = mapper.toOrmEntity(season);
const rehydrated = mapper.toDomain(orm);
expect(rehydrated.id).toBe('season-1');
expect(rehydrated.leagueId).toBe('league-1');
expect(rehydrated.gameId).toBe('iracing');
expect(rehydrated.name).toBe('Season 1');
expect(rehydrated.status.toString()).toBe('active');
expect(rehydrated.schedulePublished).toBe(true);
expect(rehydrated.getParticipantCount()).toBe(7);
});
it('maps Race domain <-> orm', () => {
const race = Race.create({
id: 'race-1',
leagueId: 'league-1',
scheduledAt: new Date('2025-01-10T12:00:00.000Z'),
track: 'Spa',
car: 'GT3',
sessionType: RaceSessionType.main(),
status: 'scheduled',
registeredCount: 3,
maxParticipants: 50,
});
const mapper = new RaceOrmMapper();
const orm = mapper.toOrmEntity(race);
const rehydrated = mapper.toDomain(orm);
expect(rehydrated.id).toBe('race-1');
expect(rehydrated.leagueId).toBe('league-1');
expect(rehydrated.track).toBe('Spa');
expect(rehydrated.car).toBe('GT3');
expect(rehydrated.sessionType.props).toBe('main');
expect(rehydrated.status.toString()).toBe('scheduled');
expect(rehydrated.getRegisteredCount()).toBe(3);
expect(rehydrated.getMaxParticipants()).toBe(50);
});
it('maps LeagueScoringConfig domain <-> orm (including PointsTable)', () => {
const pointsTableBySessionType: Record<SessionType, PointsTable> = {
practice: new PointsTable({}),
qualifying: new PointsTable({}),
q1: new PointsTable({}),
q2: new PointsTable({}),
q3: new PointsTable({}),
sprint: new PointsTable({}),
main: new PointsTable({ 1: 25, 2: 18 }),
timeTrial: new PointsTable({}),
};
const bonusRulesBySessionType = {
practice: [],
qualifying: [],
q1: [],
q2: [],
q3: [],
sprint: [],
main: [],
timeTrial: [],
};
const championship: ChampionshipConfig = {
id: 'champ-1',
name: 'Driver Championship',
type: 'driver',
sessionTypes: ['main' as SessionType],
pointsTableBySessionType,
bonusRulesBySessionType,
dropScorePolicy: { strategy: 'none' },
};
const config = LeagueScoringConfig.create({
id: 'lsc-season-1',
seasonId: 'season-1',
scoringPresetId: 'club-default',
championships: [championship],
});
const pointsTableMapper = new PointsTableJsonMapper();
const championshipMapper = new ChampionshipConfigJsonMapper(pointsTableMapper);
const mapper = new LeagueScoringConfigOrmMapper(championshipMapper);
const orm = mapper.toOrmEntity(config);
const rehydrated = mapper.toDomain(orm);
expect(rehydrated.id.toString()).toBe('lsc-season-1');
expect(rehydrated.seasonId.toString()).toBe('season-1');
expect(rehydrated.scoringPresetId?.toString()).toBe('club-default');
expect(rehydrated.championships).toHaveLength(1);
const mainPointsTable =
rehydrated.championships[0]?.pointsTableBySessionType['main' as SessionType];
expect(mainPointsTable).toBeDefined();
expect(mainPointsTable!.getPointsForPosition(1)).toBe(25);
});
});

View File

@@ -0,0 +1,49 @@
import 'reflect-metadata';
import { MODULE_METADATA } from '@nestjs/common/constants';
import { Test } from '@nestjs/testing';
import type { TestingModule } from '@nestjs/testing';
import { afterEach, describe, expect, it, vi } from 'vitest';
import { LEAGUE_REPOSITORY_TOKEN } from '../inmemory/InMemoryRacingPersistenceModule';
describe('RacingPersistenceModule', () => {
const originalEnv = { ...process.env };
afterEach(() => {
process.env = originalEnv;
vi.restoreAllMocks();
});
it('uses inmemory providers when GRIDPILOT_API_PERSISTENCE=inmemory', async () => {
vi.resetModules();
process.env.GRIDPILOT_API_PERSISTENCE = 'inmemory';
delete process.env.DATABASE_URL;
const { RacingPersistenceModule } = await import('./RacingPersistenceModule');
const { InMemoryLeagueRepository } = await import('@adapters/racing/persistence/inmemory/InMemoryLeagueRepository');
const module: TestingModule = await Test.createTestingModule({
imports: [RacingPersistenceModule],
}).compile();
const leagueRepo = module.get(LEAGUE_REPOSITORY_TOKEN);
expect(leagueRepo).toBeInstanceOf(InMemoryLeagueRepository);
await module.close();
});
it('uses postgres module when GRIDPILOT_API_PERSISTENCE=postgres', async () => {
vi.resetModules();
process.env.GRIDPILOT_API_PERSISTENCE = 'postgres';
delete process.env.DATABASE_URL;
const { RacingPersistenceModule } = await import('./RacingPersistenceModule');
const { PostgresRacingPersistenceModule } = await import('../postgres/PostgresRacingPersistenceModule');
const imports = Reflect.getMetadata(MODULE_METADATA.IMPORTS, RacingPersistenceModule) as unknown[];
expect(imports).toContain(PostgresRacingPersistenceModule);
});
});

View File

@@ -0,0 +1,14 @@
import { Module } from '@nestjs/common';
import { getApiPersistence } from '../../env';
import { InMemoryRacingPersistenceModule } from '../inmemory/InMemoryRacingPersistenceModule';
import { PostgresRacingPersistenceModule } from '../postgres/PostgresRacingPersistenceModule';
const selectedPersistenceModule =
getApiPersistence() === 'postgres' ? PostgresRacingPersistenceModule : InMemoryRacingPersistenceModule;
@Module({
imports: [selectedPersistenceModule],
exports: [selectedPersistenceModule],
})
export class RacingPersistenceModule {}