From 4d890863d374ceedf362ff75a65fadbd6d47c0db Mon Sep 17 00:00:00 2001 From: Marc Mintel Date: Wed, 17 Dec 2025 01:23:09 +0100 Subject: [PATCH] refactor --- adapters/bootstrap/LeagueConstraints.ts | 44 + .../ApproveLeagueJoinRequestUseCase.ts | 3 +- .../racing/domain/value-objects/CarId.test.ts | 34 + .../domain/value-objects/CountryCode.ts | 11 +- .../value-objects/DecalOverride.test.ts | 141 + core/racing/domain/value-objects/DriverBio.ts | 20 - core/racing/domain/value-objects/DriverId.ts | 25 - .../racing/domain/value-objects/DriverName.ts | 20 - .../value-objects/GameConstraints.test.ts | 46 + .../domain/value-objects/GameConstraints.ts | 82 +- core/racing/domain/value-objects/IRacingId.ts | 11 +- .../domain/value-objects/ImageUrl.test.ts | 34 + core/racing/domain/value-objects/ImageUrl.ts | 2 +- core/racing/domain/value-objects/JoinedAt.ts | 11 +- .../value-objects/LeagueDescription.test.ts | 61 + .../domain/value-objects/LeagueDescription.ts | 2 +- .../domain/value-objects/LeagueName.test.ts | 65 + .../value-objects/LeagueTimezone.test.ts | 37 + .../domain/value-objects/LeagueTimezone.ts | 18 +- .../value-objects/LeagueVisibility.test.ts | 61 + .../domain/value-objects/LeagueVisibility.ts | 15 - .../domain/value-objects/LiveryDecal.test.ts | 344 ++ .../domain/value-objects/LiveryDecal.ts | 29 +- .../value-objects/MembershipFee.test.ts | 73 + .../domain/value-objects/MembershipFee.ts | 38 +- .../racing/domain/value-objects/Money.test.ts | 103 + core/racing/domain/value-objects/Money.ts | 27 +- .../MonthlyRecurrencePattern.test.ts | 53 + .../value-objects/MonthlyRecurrencePattern.ts | 51 +- core/racing/domain/value-objects/Points.ts | 11 +- .../domain/value-objects/PointsTable.test.ts | 45 + .../domain/value-objects/PointsTable.ts | 6 +- .../value-objects/RaceIncidents.test.ts | 102 + .../domain/value-objects/RaceIncidents.ts | 127 +- .../value-objects/RaceTimeOfDay.test.ts | 65 + .../value-objects/RecurrenceStrategy.test.ts | 107 + .../value-objects/RecurrenceStrategy.ts | 54 +- .../value-objects/ScheduledRaceSlot.test.ts | 65 + .../value-objects/SeasonSchedule.test.ts | 112 + .../domain/value-objects/SessionType.test.ts | 86 + .../value-objects/SponsorshipPricing.test.ts | 121 + .../value-objects/SponsorshipPricing.ts | 77 - .../value-objects/TeamCreatedAt.test.ts | 31 + .../domain/value-objects/TeamCreatedAt.ts | 11 +- .../value-objects/TeamDescription.test.ts | 32 + .../domain/value-objects/TeamDescription.ts | 11 +- .../domain/value-objects/TeamName.test.ts | 32 + core/racing/domain/value-objects/TeamName.ts | 11 +- .../domain/value-objects/TeamTag.test.ts | 32 + core/racing/domain/value-objects/TeamTag.ts | 11 +- .../domain/value-objects/TrackCountry.test.ts | 32 + .../domain/value-objects/TrackCountry.ts | 11 +- .../domain/value-objects/TrackGameId.test.ts | 32 + .../domain/value-objects/TrackGameId.ts | 11 +- .../domain/value-objects/TrackId.test.ts | 32 + core/racing/domain/value-objects/TrackId.ts | 11 +- .../value-objects/TrackImageUrl.test.ts | 45 + .../domain/value-objects/TrackImageUrl.ts | 11 +- .../domain/value-objects/TrackLength.test.ts | 27 + .../domain/value-objects/TrackLength.ts | 11 +- .../domain/value-objects/TrackName.test.ts | 32 + core/racing/domain/value-objects/TrackName.ts | 11 +- .../value-objects/TrackShortName.test.ts | 32 + .../domain/value-objects/TrackShortName.ts | 11 +- .../domain/value-objects/TrackTurns.test.ts | 31 + .../racing/domain/value-objects/TrackTurns.ts | 11 +- .../{ => driver}/DriverBio.test.ts | 0 .../domain/value-objects/driver/DriverBio.ts | 29 + .../{ => driver}/DriverId.test.ts | 0 .../domain/value-objects/driver/DriverId.ts | 29 + .../{ => driver}/DriverName.test.ts | 0 .../domain/value-objects/driver/DriverName.ts | 29 + package-lock.json | 2808 +---------------- 73 files changed, 2632 insertions(+), 3224 deletions(-) create mode 100644 adapters/bootstrap/LeagueConstraints.ts create mode 100644 core/racing/domain/value-objects/CarId.test.ts create mode 100644 core/racing/domain/value-objects/DecalOverride.test.ts delete mode 100644 core/racing/domain/value-objects/DriverBio.ts delete mode 100644 core/racing/domain/value-objects/DriverId.ts delete mode 100644 core/racing/domain/value-objects/DriverName.ts create mode 100644 core/racing/domain/value-objects/GameConstraints.test.ts create mode 100644 core/racing/domain/value-objects/ImageUrl.test.ts create mode 100644 core/racing/domain/value-objects/LeagueDescription.test.ts create mode 100644 core/racing/domain/value-objects/LeagueName.test.ts create mode 100644 core/racing/domain/value-objects/LeagueTimezone.test.ts create mode 100644 core/racing/domain/value-objects/LeagueVisibility.test.ts create mode 100644 core/racing/domain/value-objects/LiveryDecal.test.ts create mode 100644 core/racing/domain/value-objects/MembershipFee.test.ts create mode 100644 core/racing/domain/value-objects/Money.test.ts create mode 100644 core/racing/domain/value-objects/MonthlyRecurrencePattern.test.ts create mode 100644 core/racing/domain/value-objects/PointsTable.test.ts create mode 100644 core/racing/domain/value-objects/RaceIncidents.test.ts create mode 100644 core/racing/domain/value-objects/RaceTimeOfDay.test.ts create mode 100644 core/racing/domain/value-objects/RecurrenceStrategy.test.ts create mode 100644 core/racing/domain/value-objects/ScheduledRaceSlot.test.ts create mode 100644 core/racing/domain/value-objects/SeasonSchedule.test.ts create mode 100644 core/racing/domain/value-objects/SessionType.test.ts create mode 100644 core/racing/domain/value-objects/SponsorshipPricing.test.ts create mode 100644 core/racing/domain/value-objects/TeamCreatedAt.test.ts create mode 100644 core/racing/domain/value-objects/TeamDescription.test.ts create mode 100644 core/racing/domain/value-objects/TeamName.test.ts create mode 100644 core/racing/domain/value-objects/TeamTag.test.ts create mode 100644 core/racing/domain/value-objects/TrackCountry.test.ts create mode 100644 core/racing/domain/value-objects/TrackGameId.test.ts create mode 100644 core/racing/domain/value-objects/TrackId.test.ts create mode 100644 core/racing/domain/value-objects/TrackImageUrl.test.ts create mode 100644 core/racing/domain/value-objects/TrackLength.test.ts create mode 100644 core/racing/domain/value-objects/TrackName.test.ts create mode 100644 core/racing/domain/value-objects/TrackShortName.test.ts create mode 100644 core/racing/domain/value-objects/TrackTurns.test.ts rename core/racing/domain/value-objects/{ => driver}/DriverBio.test.ts (100%) create mode 100644 core/racing/domain/value-objects/driver/DriverBio.ts rename core/racing/domain/value-objects/{ => driver}/DriverId.test.ts (100%) create mode 100644 core/racing/domain/value-objects/driver/DriverId.ts rename core/racing/domain/value-objects/{ => driver}/DriverName.test.ts (100%) create mode 100644 core/racing/domain/value-objects/driver/DriverName.ts diff --git a/adapters/bootstrap/LeagueConstraints.ts b/adapters/bootstrap/LeagueConstraints.ts new file mode 100644 index 000000000..e18450f05 --- /dev/null +++ b/adapters/bootstrap/LeagueConstraints.ts @@ -0,0 +1,44 @@ +export const LEAGUE_DESCRIPTION_CONSTRAINTS = { + minLength: 20, + maxLength: 1000, + recommendedMinLength: 50, +} as const; + +export const LEAGUE_NAME_CONSTRAINTS = { + minLength: 3, + maxLength: 64, + pattern: /^[a-zA-Z0-9].*$/, // Must start with alphanumeric + forbiddenPatterns: [ + /^\s/, // No leading whitespace + /\s$/, // No trailing whitespace + /\s{2,}/, // No multiple consecutive spaces + ], +} as const; + +export type LeagueVisibilityType = 'ranked' | 'unranked'; + +export interface LeagueVisibilityConstraints { + readonly minDrivers: number; + readonly isPubliclyVisible: boolean; + readonly affectsRatings: boolean; + readonly requiresApproval: boolean; +} + +export const VISIBILITY_CONSTRAINTS: Record = { + ranked: { + minDrivers: 10, + isPubliclyVisible: true, + affectsRatings: true, + requiresApproval: false, // Anyone can join public leagues + }, + unranked: { + minDrivers: 2, + isPubliclyVisible: false, + affectsRatings: false, + requiresApproval: true, // Private leagues require invite/approval + }, +}; + +// Export constants for validation +export const MIN_RANKED_LEAGUE_DRIVERS = VISIBILITY_CONSTRAINTS.ranked.minDrivers; +export const MIN_UNRANKED_LEAGUE_DRIVERS = VISIBILITY_CONSTRAINTS.unranked.minDrivers; \ No newline at end of file diff --git a/core/racing/application/use-cases/ApproveLeagueJoinRequestUseCase.ts b/core/racing/application/use-cases/ApproveLeagueJoinRequestUseCase.ts index 81ebf226e..790ca56f8 100644 --- a/core/racing/application/use-cases/ApproveLeagueJoinRequestUseCase.ts +++ b/core/racing/application/use-cases/ApproveLeagueJoinRequestUseCase.ts @@ -5,6 +5,7 @@ import type { AsyncUseCase } from '@core/shared/application'; import { randomUUID } from 'crypto'; import type { ApproveLeagueJoinRequestUseCaseParams } from '../dto/ApproveLeagueJoinRequestUseCaseParams'; import type { ApproveLeagueJoinRequestResultDTO } from '../dto/ApproveLeagueJoinRequestResultDTO'; +import { JoinedAt } from '../../domain/value-objects/JoinedAt'; export class ApproveLeagueJoinRequestUseCase implements AsyncUseCase { constructor(private readonly leagueMembershipRepository: ILeagueMembershipRepository) {} @@ -22,7 +23,7 @@ export class ApproveLeagueJoinRequestUseCase implements AsyncUseCase { + it('should create a car id', () => { + const id = CarId.create('car1'); + expect(id.toString()).toBe('car1'); + }); + + it('should throw on empty id', () => { + expect(() => CarId.create('')).toThrow('Car ID cannot be empty'); + }); + + it('should throw on whitespace id', () => { + expect(() => CarId.create(' ')).toThrow('Car ID cannot be empty'); + }); + + it('should trim whitespace', () => { + const id = CarId.create(' car1 '); + expect(id.toString()).toBe('car1'); + }); + + it('should equal same id', () => { + const id1 = CarId.create('car1'); + const id2 = CarId.create('car1'); + expect(id1.equals(id2)).toBe(true); + }); + + it('should not equal different id', () => { + const id1 = CarId.create('car1'); + const id2 = CarId.create('car2'); + expect(id1.equals(id2)).toBe(false); + }); +}); \ No newline at end of file diff --git a/core/racing/domain/value-objects/CountryCode.ts b/core/racing/domain/value-objects/CountryCode.ts index 1a6f69e7c..cc3f82b3d 100644 --- a/core/racing/domain/value-objects/CountryCode.ts +++ b/core/racing/domain/value-objects/CountryCode.ts @@ -1,6 +1,7 @@ import { RacingDomainValidationError } from '../errors/RacingDomainError'; +import type { IValueObject } from '@core/shared/domain'; -export class CountryCode { +export class CountryCode implements IValueObject { private constructor(private readonly value: string) {} static create(value: string): CountryCode { @@ -18,7 +19,11 @@ export class CountryCode { return this.value; } - equals(other: CountryCode): boolean { - return this.value === other.value; + equals(other: IValueObject): boolean { + return this.value === other.props; + } + + get props(): string { + return this.value; } } \ No newline at end of file diff --git a/core/racing/domain/value-objects/DecalOverride.test.ts b/core/racing/domain/value-objects/DecalOverride.test.ts new file mode 100644 index 000000000..888fe121d --- /dev/null +++ b/core/racing/domain/value-objects/DecalOverride.test.ts @@ -0,0 +1,141 @@ +import { describe, it, expect } from 'vitest'; +import { DecalOverride } from './DecalOverride'; + +describe('DecalOverride', () => { + it('should create a decal override', () => { + const override = DecalOverride.create({ + leagueId: 'league1', + seasonId: 'season1', + decalId: 'decal1', + newX: 0.5, + newY: 0.3, + }); + expect(override.props).toEqual({ + leagueId: 'league1', + seasonId: 'season1', + decalId: 'decal1', + newX: 0.5, + newY: 0.3, + }); + }); + + it('should throw on empty leagueId', () => { + expect(() => + DecalOverride.create({ + leagueId: '', + seasonId: 'season1', + decalId: 'decal1', + newX: 0.5, + newY: 0.3, + }) + ).toThrow('DecalOverride leagueId is required'); + }); + + it('should throw on empty seasonId', () => { + expect(() => + DecalOverride.create({ + leagueId: 'league1', + seasonId: '', + decalId: 'decal1', + newX: 0.5, + newY: 0.3, + }) + ).toThrow('DecalOverride seasonId is required'); + }); + + it('should throw on empty decalId', () => { + expect(() => + DecalOverride.create({ + leagueId: 'league1', + seasonId: 'season1', + decalId: '', + newX: 0.5, + newY: 0.3, + }) + ).toThrow('DecalOverride decalId is required'); + }); + + it('should throw on newX less than 0', () => { + expect(() => + DecalOverride.create({ + leagueId: 'league1', + seasonId: 'season1', + decalId: 'decal1', + newX: -0.1, + newY: 0.3, + }) + ).toThrow('DecalOverride newX must be between 0 and 1'); + }); + + it('should throw on newX greater than 1', () => { + expect(() => + DecalOverride.create({ + leagueId: 'league1', + seasonId: 'season1', + decalId: 'decal1', + newX: 1.1, + newY: 0.3, + }) + ).toThrow('DecalOverride newX must be between 0 and 1'); + }); + + it('should throw on newY less than 0', () => { + expect(() => + DecalOverride.create({ + leagueId: 'league1', + seasonId: 'season1', + decalId: 'decal1', + newX: 0.5, + newY: -0.1, + }) + ).toThrow('DecalOverride newY must be between 0 and 1'); + }); + + it('should throw on newY greater than 1', () => { + expect(() => + DecalOverride.create({ + leagueId: 'league1', + seasonId: 'season1', + decalId: 'decal1', + newX: 0.5, + newY: 1.1, + }) + ).toThrow('DecalOverride newY must be between 0 and 1'); + }); + + it('should equal same override', () => { + const o1 = DecalOverride.create({ + leagueId: 'league1', + seasonId: 'season1', + decalId: 'decal1', + newX: 0.5, + newY: 0.3, + }); + const o2 = DecalOverride.create({ + leagueId: 'league1', + seasonId: 'season1', + decalId: 'decal1', + newX: 0.5, + newY: 0.3, + }); + expect(o1.equals(o2)).toBe(true); + }); + + it('should not equal different override', () => { + const o1 = DecalOverride.create({ + leagueId: 'league1', + seasonId: 'season1', + decalId: 'decal1', + newX: 0.5, + newY: 0.3, + }); + const o2 = DecalOverride.create({ + leagueId: 'league2', + seasonId: 'season1', + decalId: 'decal1', + newX: 0.5, + newY: 0.3, + }); + expect(o1.equals(o2)).toBe(false); + }); +}); \ No newline at end of file diff --git a/core/racing/domain/value-objects/DriverBio.ts b/core/racing/domain/value-objects/DriverBio.ts deleted file mode 100644 index 1683bca1d..000000000 --- a/core/racing/domain/value-objects/DriverBio.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { RacingDomainValidationError } from '../errors/RacingDomainError'; - -export class DriverBio { - private constructor(private readonly value: string) {} - - static create(value: string): DriverBio { - if (value.length > 500) { - throw new RacingDomainValidationError('Driver bio cannot exceed 500 characters'); - } - return new DriverBio(value); - } - - toString(): string { - return this.value; - } - - equals(other: DriverBio): boolean { - return this.value === other.value; - } -} \ No newline at end of file diff --git a/core/racing/domain/value-objects/DriverId.ts b/core/racing/domain/value-objects/DriverId.ts deleted file mode 100644 index 455ce18a2..000000000 --- a/core/racing/domain/value-objects/DriverId.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { RacingDomainValidationError } from '../errors/RacingDomainError'; -import type { IValueObject } from '@core/shared/domain'; - -export class DriverId implements IValueObject { - private constructor(private readonly value: string) {} - - static create(value: string): DriverId { - if (!value || value.trim().length === 0) { - throw new RacingDomainValidationError('Driver ID cannot be empty'); - } - return new DriverId(value.trim()); - } - - toString(): string { - return this.value; - } - - equals(other: IValueObject): boolean { - return this.value === other.props; - } - - get props(): string { - return this.value; - } -} \ No newline at end of file diff --git a/core/racing/domain/value-objects/DriverName.ts b/core/racing/domain/value-objects/DriverName.ts deleted file mode 100644 index 85cbebfbf..000000000 --- a/core/racing/domain/value-objects/DriverName.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { RacingDomainValidationError } from '../errors/RacingDomainError'; - -export class DriverName { - private constructor(private readonly value: string) {} - - static create(value: string): DriverName { - if (!value || value.trim().length === 0) { - throw new RacingDomainValidationError('Driver name is required'); - } - return new DriverName(value.trim()); - } - - toString(): string { - return this.value; - } - - equals(other: DriverName): boolean { - return this.value === other.value; - } -} \ No newline at end of file diff --git a/core/racing/domain/value-objects/GameConstraints.test.ts b/core/racing/domain/value-objects/GameConstraints.test.ts new file mode 100644 index 000000000..5f4451e3b --- /dev/null +++ b/core/racing/domain/value-objects/GameConstraints.test.ts @@ -0,0 +1,46 @@ +import { describe, it, expect } from 'vitest'; +import { GameConstraints } from './GameConstraints'; + +const sampleConstraints = { + maxDrivers: 64, + maxTeams: 32, + defaultMaxDrivers: 24, + minDrivers: 2, + supportsTeams: true, + supportsMultiClass: true, +}; + +describe('GameConstraints', () => { + it('should create', () => { + const gc = new GameConstraints('iracing', sampleConstraints); + expect(gc.gameId).toBe('iracing'); + expect(gc.maxDrivers).toBe(64); + }); + + it('should validate driver count', () => { + const gc = new GameConstraints('iracing', sampleConstraints); + expect(gc.validateDriverCount(10)).toEqual({ valid: true }); + expect(gc.validateDriverCount(1)).toEqual({ valid: false, error: 'Minimum 2 drivers required' }); + expect(gc.validateDriverCount(100)).toEqual({ valid: false, error: 'Maximum 64 drivers allowed for IRACING' }); + }); + + it('should validate team count', () => { + const gc = new GameConstraints('iracing', sampleConstraints); + expect(gc.validateTeamCount(10)).toEqual({ valid: true }); + expect(gc.validateTeamCount(100)).toEqual({ valid: false, error: 'Maximum 32 teams allowed for IRACING' }); + }); + + it('should not support teams if false', () => { + const noTeams = { ...sampleConstraints, supportsTeams: false }; + const gc = new GameConstraints('acc', noTeams); + expect(gc.validateTeamCount(1)).toEqual({ valid: false, error: 'ACC does not support team-based leagues' }); + }); + + it('equals', () => { + const gc1 = new GameConstraints('iracing', sampleConstraints); + const gc2 = new GameConstraints('iracing', sampleConstraints); + const gc3 = new GameConstraints('acc', sampleConstraints); + expect(gc1.equals(gc2)).toBe(true); + expect(gc1.equals(gc3)).toBe(false); + }); +}); \ No newline at end of file diff --git a/core/racing/domain/value-objects/GameConstraints.ts b/core/racing/domain/value-objects/GameConstraints.ts index d9f7fe51d..a12022bed 100644 --- a/core/racing/domain/value-objects/GameConstraints.ts +++ b/core/racing/domain/value-objects/GameConstraints.ts @@ -21,75 +21,11 @@ export interface GameConstraintsProps { constraints: GameConstraintsData; } -/** - * Game-specific constraints for popular sim racing games - */ -const GAME_CONSTRAINTS: Record & { default: GameConstraintsData } = { - iracing: { - maxDrivers: 64, - maxTeams: 32, - defaultMaxDrivers: 24, - minDrivers: 2, - supportsTeams: true, - supportsMultiClass: true, - }, - acc: { - maxDrivers: 30, - maxTeams: 15, - defaultMaxDrivers: 24, - minDrivers: 2, - supportsTeams: true, - supportsMultiClass: false, - }, - rf2: { - maxDrivers: 64, - maxTeams: 32, - defaultMaxDrivers: 24, - minDrivers: 2, - supportsTeams: true, - supportsMultiClass: true, - }, - ams2: { - maxDrivers: 32, - maxTeams: 16, - defaultMaxDrivers: 20, - minDrivers: 2, - supportsTeams: true, - supportsMultiClass: true, - }, - lmu: { - maxDrivers: 32, - maxTeams: 16, - defaultMaxDrivers: 24, - minDrivers: 2, - supportsTeams: true, - supportsMultiClass: true, - }, - // Default for unknown games - default: { - maxDrivers: 32, - maxTeams: 16, - defaultMaxDrivers: 20, - minDrivers: 2, - supportsTeams: true, - supportsMultiClass: false, - }, -}; - -function getConstraintsForId(gameId: string): GameConstraintsData { - const lower = gameId.toLowerCase(); - const fromMap = GAME_CONSTRAINTS[lower]; - if (fromMap) { - return fromMap; - } - return GAME_CONSTRAINTS.default; -} - export class GameConstraints implements IValueObject { readonly gameId: string; readonly constraints: GameConstraintsData; - private constructor(gameId: string, constraints: GameConstraintsData) { + constructor(gameId: string, constraints: GameConstraintsData) { this.gameId = gameId; this.constraints = constraints; } @@ -105,22 +41,6 @@ export class GameConstraints implements IValueObject { return this.props.gameId === other.props.gameId; } - /** - * Get constraints for a specific game - */ - static forGame(gameId: string): GameConstraints { - const constraints = getConstraintsForId(gameId); - const lowerId = gameId.toLowerCase(); - return new GameConstraints(lowerId, constraints); - } - - /** - * Get all supported game IDs - */ - static getSupportedGames(): string[] { - return Object.keys(GAME_CONSTRAINTS).filter(id => id !== 'default'); - } - /** * Maximum drivers allowed for this game */ diff --git a/core/racing/domain/value-objects/IRacingId.ts b/core/racing/domain/value-objects/IRacingId.ts index c59eb36dc..0a6695266 100644 --- a/core/racing/domain/value-objects/IRacingId.ts +++ b/core/racing/domain/value-objects/IRacingId.ts @@ -1,6 +1,7 @@ import { RacingDomainValidationError } from '../errors/RacingDomainError'; +import type { IValueObject } from '@core/shared/domain'; -export class IRacingId { +export class IRacingId implements IValueObject { private constructor(private readonly value: string) {} static create(value: string): IRacingId { @@ -14,7 +15,11 @@ export class IRacingId { return this.value; } - equals(other: IRacingId): boolean { - return this.value === other.value; + get props(): string { + return this.value; + } + + equals(other: IValueObject): boolean { + return this.props === other.props; } } \ No newline at end of file diff --git a/core/racing/domain/value-objects/ImageUrl.test.ts b/core/racing/domain/value-objects/ImageUrl.test.ts new file mode 100644 index 000000000..8be41029a --- /dev/null +++ b/core/racing/domain/value-objects/ImageUrl.test.ts @@ -0,0 +1,34 @@ +import { describe, it, expect } from 'vitest'; +import { ImageUrl } from './ImageUrl'; + +describe('ImageUrl', () => { + it('should create valid url', () => { + const url = ImageUrl.create('https://example.com/image.jpg'); + expect(url.toString()).toBe('https://example.com/image.jpg'); + }); + + it('should throw on empty', () => { + expect(() => ImageUrl.create('')).toThrow('Image URL cannot be empty'); + }); + + it('should throw on invalid url', () => { + expect(() => ImageUrl.create('not-a-url')).toThrow('Invalid image URL format'); + }); + + it('should trim', () => { + const url = ImageUrl.create(' https://example.com '); + expect(url.toString()).toBe('https://example.com'); + }); + + it('should equal same', () => { + const u1 = ImageUrl.create('https://example.com'); + const u2 = ImageUrl.create('https://example.com'); + expect(u1.equals(u2)).toBe(true); + }); + + it('should not equal different', () => { + const u1 = ImageUrl.create('https://example.com/1'); + const u2 = ImageUrl.create('https://example.com/2'); + expect(u1.equals(u2)).toBe(false); + }); +}); \ No newline at end of file diff --git a/core/racing/domain/value-objects/ImageUrl.ts b/core/racing/domain/value-objects/ImageUrl.ts index df530b03b..b0f42ee34 100644 --- a/core/racing/domain/value-objects/ImageUrl.ts +++ b/core/racing/domain/value-objects/ImageUrl.ts @@ -22,7 +22,7 @@ export class ImageUrl implements IValueObject { } equals(other: IValueObject): boolean { - return this.value === other.props; + return this.props === other.props; } get props(): string { diff --git a/core/racing/domain/value-objects/JoinedAt.ts b/core/racing/domain/value-objects/JoinedAt.ts index 31a970ca5..503766108 100644 --- a/core/racing/domain/value-objects/JoinedAt.ts +++ b/core/racing/domain/value-objects/JoinedAt.ts @@ -1,6 +1,7 @@ import { RacingDomainValidationError } from '../errors/RacingDomainError'; +import type { IValueObject } from '@core/shared/domain'; -export class JoinedAt { +export class JoinedAt implements IValueObject { private constructor(private readonly value: Date) {} static create(value: Date): JoinedAt { @@ -15,7 +16,11 @@ export class JoinedAt { return new Date(this.value); } - equals(other: JoinedAt): boolean { - return this.value.getTime() === other.value.getTime(); + get props(): Date { + return new Date(this.value); + } + + equals(other: IValueObject): boolean { + return this.props.getTime() === other.props.getTime(); } } \ No newline at end of file diff --git a/core/racing/domain/value-objects/LeagueDescription.test.ts b/core/racing/domain/value-objects/LeagueDescription.test.ts new file mode 100644 index 000000000..9c16b87f3 --- /dev/null +++ b/core/racing/domain/value-objects/LeagueDescription.test.ts @@ -0,0 +1,61 @@ +import { describe, it, expect } from 'vitest'; +import { LeagueDescription } from './LeagueDescription'; + +describe('LeagueDescription', () => { + it('should create valid description', () => { + const desc = LeagueDescription.create('This is a valid league description with enough characters.'); + expect(desc.value).toBe('This is a valid league description with enough characters.'); + }); + + it('should trim whitespace', () => { + const desc = LeagueDescription.create(' This is a valid description with enough characters to pass validation. '); + expect(desc.value).toBe('This is a valid description with enough characters to pass validation.'); + }); + + it('should validate minimum length', () => { + expect(() => LeagueDescription.create('Short')).toThrow('Description must be at least 20 characters'); + }); + + it('should validate maximum length', () => { + const longDesc = 'a'.repeat(1001); + expect(() => LeagueDescription.create(longDesc)).toThrow('Description must be 1000 characters or less'); + }); + + it('should validate required', () => { + expect(() => LeagueDescription.create('')).toThrow('Description is required'); + expect(() => LeagueDescription.create(' ')).toThrow('Description is required'); + }); + + it('should check recommended length', () => { + expect(LeagueDescription.isRecommendedLength('Short desc')).toBe(false); + expect(LeagueDescription.isRecommendedLength('This is a longer description that meets the recommended length.')).toBe(true); + }); + + it('should validate without creating', () => { + expect(LeagueDescription.validate('Valid')).toEqual({ valid: false, error: 'Description must be at least 20 characters — tell drivers what makes your league special' }); + expect(LeagueDescription.validate('This is a valid description.')).toEqual({ valid: true }); + }); + + it('should tryCreate', () => { + expect(LeagueDescription.tryCreate('This is a valid description with enough characters.')).toBeInstanceOf(LeagueDescription); + expect(LeagueDescription.tryCreate('Short')).toBeNull(); + }); + + it('should have props', () => { + const desc = LeagueDescription.create('This is a test description with enough characters.'); + expect(desc.props).toEqual({ value: 'This is a test description with enough characters.' }); + }); + + it('should toString', () => { + const desc = LeagueDescription.create('This is a test description.'); + expect(desc.toString()).toBe('This is a test description.'); + }); + + it('equals', () => { + const desc1 = LeagueDescription.create('This is a test description.'); + const desc2 = LeagueDescription.create('This is a test description.'); + const desc3 = LeagueDescription.create('This is a different description with enough characters.'); + expect(desc1.equals(desc2)).toBe(true); + expect(desc1.equals(desc3)).toBe(false); + }); +}); \ No newline at end of file diff --git a/core/racing/domain/value-objects/LeagueDescription.ts b/core/racing/domain/value-objects/LeagueDescription.ts index b97e6d17b..327e2a907 100644 --- a/core/racing/domain/value-objects/LeagueDescription.ts +++ b/core/racing/domain/value-objects/LeagueDescription.ts @@ -3,7 +3,7 @@ * * Represents a valid league description with validation rules. */ - + import { RacingDomainValidationError } from '../errors/RacingDomainError'; import type { IValueObject } from '@core/shared/domain'; diff --git a/core/racing/domain/value-objects/LeagueName.test.ts b/core/racing/domain/value-objects/LeagueName.test.ts new file mode 100644 index 000000000..1468be3da --- /dev/null +++ b/core/racing/domain/value-objects/LeagueName.test.ts @@ -0,0 +1,65 @@ +import { describe, it, expect } from 'vitest'; +import { LeagueName } from './LeagueName'; + +describe('LeagueName', () => { + it('should create valid name', () => { + const name = LeagueName.create('Valid League Name'); + expect(name.value).toBe('Valid League Name'); + }); + + it('should trim whitespace', () => { + const name = LeagueName.create('Valid Name'); + expect(name.value).toBe('Valid Name'); + }); + + it('should validate minimum length', () => { + expect(() => LeagueName.create('AB')).toThrow('League name must be at least 3 characters'); + }); + + it('should validate maximum length', () => { + const longName = 'a'.repeat(65); + expect(() => LeagueName.create(longName)).toThrow('League name must be 64 characters or less'); + }); + + it('should validate required', () => { + expect(() => LeagueName.create('')).toThrow('League name is required'); + expect(() => LeagueName.create(' ')).toThrow('League name is required'); + }); + + it('should validate pattern', () => { + expect(() => LeagueName.create('_league')).toThrow('League name must start with a letter or number'); + }); + + it('should validate forbidden patterns', () => { + expect(() => LeagueName.create(' League ')).toThrow('League name cannot have leading/trailing spaces or multiple consecutive spaces'); + expect(() => LeagueName.create('League Name')).toThrow('League name cannot have leading/trailing spaces or multiple consecutive spaces'); + }); + + it('should validate without creating', () => { + expect(LeagueName.validate('AB')).toEqual({ valid: false, error: 'League name must be at least 3 characters' }); + expect(LeagueName.validate('Valid Name')).toEqual({ valid: true }); + }); + + it('should tryCreate', () => { + expect(LeagueName.tryCreate('Valid')).toBeInstanceOf(LeagueName); + expect(LeagueName.tryCreate('AB')).toBeNull(); + }); + + it('should have props', () => { + const name = LeagueName.create('Test'); + expect(name.props).toEqual({ value: 'Test' }); + }); + + it('should toString', () => { + const name = LeagueName.create('Test'); + expect(name.toString()).toBe('Test'); + }); + + it('equals', () => { + const name1 = LeagueName.create('Test'); + const name2 = LeagueName.create('Test'); + const name3 = LeagueName.create('Different'); + expect(name1.equals(name2)).toBe(true); + expect(name1.equals(name3)).toBe(false); + }); +}); \ No newline at end of file diff --git a/core/racing/domain/value-objects/LeagueTimezone.test.ts b/core/racing/domain/value-objects/LeagueTimezone.test.ts new file mode 100644 index 000000000..bc5e6c9cf --- /dev/null +++ b/core/racing/domain/value-objects/LeagueTimezone.test.ts @@ -0,0 +1,37 @@ +import { describe, it, expect } from 'vitest'; +import { LeagueTimezone } from './LeagueTimezone'; + +describe('LeagueTimezone', () => { + it('should create valid timezone', () => { + const tz = LeagueTimezone.create('America/New_York'); + expect(tz.id).toBe('America/New_York'); + }); + + it('should trim whitespace', () => { + const tz = LeagueTimezone.create(' America/New_York '); + expect(tz.id).toBe('America/New_York'); + }); + + it('should validate non-empty', () => { + expect(() => LeagueTimezone.create('')).toThrow('LeagueTimezone id must be a non-empty string'); + expect(() => LeagueTimezone.create(' ')).toThrow('LeagueTimezone id must be a non-empty string'); + }); + + it('should have props', () => { + const tz = LeagueTimezone.create('UTC'); + expect(tz.props).toEqual({ id: 'UTC' }); + }); + + it('should toString', () => { + const tz = LeagueTimezone.create('UTC'); + expect(tz.toString()).toBe('UTC'); + }); + + it('equals', () => { + const tz1 = LeagueTimezone.create('UTC'); + const tz2 = LeagueTimezone.create('UTC'); + const tz3 = LeagueTimezone.create('EST'); + expect(tz1.equals(tz2)).toBe(true); + expect(tz1.equals(tz3)).toBe(false); + }); +}); \ No newline at end of file diff --git a/core/racing/domain/value-objects/LeagueTimezone.ts b/core/racing/domain/value-objects/LeagueTimezone.ts index 97472e7d3..3d9637174 100644 --- a/core/racing/domain/value-objects/LeagueTimezone.ts +++ b/core/racing/domain/value-objects/LeagueTimezone.ts @@ -6,23 +6,27 @@ export interface LeagueTimezoneProps { } export class LeagueTimezone implements IValueObject { - private readonly id: string; + readonly id: string; - constructor(id: string) { - if (!id || id.trim().length === 0) { - throw new RacingDomainValidationError('LeagueTimezone id must be a non-empty string'); - } + private constructor(id: string) { this.id = id; } - getId(): string { - return this.id; + static create(id: string): LeagueTimezone { + if (!id || id.trim().length === 0) { + throw new RacingDomainValidationError('LeagueTimezone id must be a non-empty string'); + } + return new LeagueTimezone(id.trim()); } get props(): LeagueTimezoneProps { return { id: this.id }; } + toString(): string { + return this.id; + } + equals(other: IValueObject): boolean { return this.props.id === other.props.id; } diff --git a/core/racing/domain/value-objects/LeagueVisibility.test.ts b/core/racing/domain/value-objects/LeagueVisibility.test.ts new file mode 100644 index 000000000..636d45f6a --- /dev/null +++ b/core/racing/domain/value-objects/LeagueVisibility.test.ts @@ -0,0 +1,61 @@ +import { describe, it, expect } from 'vitest'; +import { LeagueVisibility } from './LeagueVisibility'; + +describe('LeagueVisibility', () => { + it('should create ranked', () => { + const vis = LeagueVisibility.ranked(); + expect(vis.type).toBe('ranked'); + expect(vis.isRanked()).toBe(true); + expect(vis.isUnranked()).toBe(false); + }); + + it('should create unranked', () => { + const vis = LeagueVisibility.unranked(); + expect(vis.type).toBe('unranked'); + expect(vis.isRanked()).toBe(false); + expect(vis.isUnranked()).toBe(true); + }); + + it('should fromString', () => { + expect(LeagueVisibility.fromString('ranked').type).toBe('ranked'); + expect(LeagueVisibility.fromString('unranked').type).toBe('unranked'); + expect(LeagueVisibility.fromString('public').type).toBe('ranked'); + expect(LeagueVisibility.fromString('private').type).toBe('unranked'); + expect(() => LeagueVisibility.fromString('invalid')).toThrow('Invalid league visibility: invalid'); + }); + + it('should validate driver count', () => { + const ranked = LeagueVisibility.ranked(); + expect(ranked.validateDriverCount(15)).toEqual({ valid: true }); + expect(ranked.validateDriverCount(5)).toEqual({ valid: false, error: 'Ranked leagues require at least 10 drivers' }); + + const unranked = LeagueVisibility.unranked(); + expect(unranked.validateDriverCount(5)).toEqual({ valid: true }); + expect(unranked.validateDriverCount(1)).toEqual({ valid: false, error: 'Unranked leagues require at least 2 drivers' }); + }); + + it('should have props', () => { + const vis = LeagueVisibility.ranked(); + expect(vis.props).toEqual({ type: 'ranked' }); + }); + + it('should toString', () => { + const vis = LeagueVisibility.ranked(); + expect(vis.toString()).toBe('ranked'); + }); + + it('should toLegacyString', () => { + const ranked = LeagueVisibility.ranked(); + const unranked = LeagueVisibility.unranked(); + expect(ranked.toLegacyString()).toBe('public'); + expect(unranked.toLegacyString()).toBe('private'); + }); + + it('equals', () => { + const vis1 = LeagueVisibility.ranked(); + const vis2 = LeagueVisibility.ranked(); + const vis3 = LeagueVisibility.unranked(); + expect(vis1.equals(vis2)).toBe(true); + expect(vis1.equals(vis3)).toBe(false); + }); +}); \ No newline at end of file diff --git a/core/racing/domain/value-objects/LeagueVisibility.ts b/core/racing/domain/value-objects/LeagueVisibility.ts index e7ee2e1fa..812603771 100644 --- a/core/racing/domain/value-objects/LeagueVisibility.ts +++ b/core/racing/domain/value-objects/LeagueVisibility.ts @@ -96,21 +96,6 @@ export class LeagueVisibility implements IValueObject { return this.type === 'unranked'; } - /** - * Human-readable label for UI display - */ - getLabel(): string { - return this.type === 'ranked' ? 'Ranked (Public)' : 'Unranked (Friends)'; - } - - /** - * Short description for UI tooltips - */ - getDescription(): string { - return this.type === 'ranked' - ? 'Competitive league visible to everyone. Results affect driver ratings.' - : 'Private league for friends. Results do not affect ratings.'; - } /** * Convert to string for serialization diff --git a/core/racing/domain/value-objects/LiveryDecal.test.ts b/core/racing/domain/value-objects/LiveryDecal.test.ts new file mode 100644 index 000000000..71ec923f3 --- /dev/null +++ b/core/racing/domain/value-objects/LiveryDecal.test.ts @@ -0,0 +1,344 @@ +import { describe, it, expect } from 'vitest'; +import { LiveryDecal } from './LiveryDecal'; + +describe('LiveryDecal', () => { + it('should create valid decal', () => { + const decal = LiveryDecal.create({ + id: 'test-id', + imageUrl: 'http://example.com/image.png', + x: 0.5, + y: 0.5, + width: 0.2, + height: 0.1, + zIndex: 1, + type: 'sponsor', + }); + expect(decal.id).toBe('test-id'); + expect(decal.imageUrl).toBe('http://example.com/image.png'); + expect(decal.x).toBe(0.5); + expect(decal.y).toBe(0.5); + expect(decal.width).toBe(0.2); + expect(decal.height).toBe(0.1); + expect(decal.rotation).toBe(0); + expect(decal.zIndex).toBe(1); + expect(decal.type).toBe('sponsor'); + }); + + it('should create with custom rotation', () => { + const decal = LiveryDecal.create({ + id: 'test-id', + imageUrl: 'http://example.com/image.png', + x: 0.5, + y: 0.5, + width: 0.2, + height: 0.1, + rotation: 45, + zIndex: 1, + type: 'user', + }); + expect(decal.rotation).toBe(45); + }); + + it('should validate id required', () => { + expect(() => LiveryDecal.create({ + id: '', + imageUrl: 'http://example.com/image.png', + x: 0.5, + y: 0.5, + width: 0.2, + height: 0.1, + zIndex: 1, + type: 'sponsor', + })).toThrow('LiveryDecal ID is required'); + }); + + it('should validate imageUrl required', () => { + expect(() => LiveryDecal.create({ + id: 'test-id', + imageUrl: '', + x: 0.5, + y: 0.5, + width: 0.2, + height: 0.1, + zIndex: 1, + type: 'sponsor', + })).toThrow('LiveryDecal imageUrl is required'); + }); + + it('should validate x coordinate range', () => { + expect(() => LiveryDecal.create({ + id: 'test-id', + imageUrl: 'http://example.com/image.png', + x: -0.1, + y: 0.5, + width: 0.2, + height: 0.1, + zIndex: 1, + type: 'sponsor', + })).toThrow('LiveryDecal x coordinate must be between 0 and 1'); + }); + + it('should validate y coordinate range', () => { + expect(() => LiveryDecal.create({ + id: 'test-id', + imageUrl: 'http://example.com/image.png', + x: 0.5, + y: 1.1, + width: 0.2, + height: 0.1, + zIndex: 1, + type: 'sponsor', + })).toThrow('LiveryDecal y coordinate must be between 0 and 1'); + }); + + it('should validate width range', () => { + expect(() => LiveryDecal.create({ + id: 'test-id', + imageUrl: 'http://example.com/image.png', + x: 0.5, + y: 0.5, + width: 0, + height: 0.1, + zIndex: 1, + type: 'sponsor', + })).toThrow('LiveryDecal width must be between 0 and 1'); + }); + + it('should validate height range', () => { + expect(() => LiveryDecal.create({ + id: 'test-id', + imageUrl: 'http://example.com/image.png', + x: 0.5, + y: 0.5, + width: 0.2, + height: 1.1, + zIndex: 1, + type: 'sponsor', + })).toThrow('LiveryDecal height must be between 0 and 1'); + }); + + it('should validate zIndex non-negative integer', () => { + expect(() => LiveryDecal.create({ + id: 'test-id', + imageUrl: 'http://example.com/image.png', + x: 0.5, + y: 0.5, + width: 0.2, + height: 0.1, + zIndex: -1, + type: 'sponsor', + })).toThrow('LiveryDecal zIndex must be a non-negative integer'); + }); + + it('should validate rotation range', () => { + expect(() => LiveryDecal.create({ + id: 'test-id', + imageUrl: 'http://example.com/image.png', + x: 0.5, + y: 0.5, + width: 0.2, + height: 0.1, + rotation: 361, + zIndex: 1, + type: 'sponsor', + })).toThrow('LiveryDecal rotation must be between 0 and 360 degrees'); + }); + + it('should validate type required', () => { + expect(() => LiveryDecal.create({ + id: 'test-id', + imageUrl: 'http://example.com/image.png', + x: 0.5, + y: 0.5, + width: 0.2, + height: 0.1, + zIndex: 1, + type: '' as 'sponsor', + })).toThrow('LiveryDecal type is required'); + }); + + it('should move to new position', () => { + const decal = LiveryDecal.create({ + id: 'test-id', + imageUrl: 'http://example.com/image.png', + x: 0.5, + y: 0.5, + width: 0.2, + height: 0.1, + zIndex: 1, + type: 'sponsor', + }); + const moved = decal.moveTo(0.3, 0.7); + expect(moved.x).toBe(0.3); + expect(moved.y).toBe(0.7); + expect(moved.id).toBe(decal.id); + }); + + it('should resize', () => { + const decal = LiveryDecal.create({ + id: 'test-id', + imageUrl: 'http://example.com/image.png', + x: 0.5, + y: 0.5, + width: 0.2, + height: 0.1, + zIndex: 1, + type: 'sponsor', + }); + const resized = decal.resize(0.4, 0.2); + expect(resized.width).toBe(0.4); + expect(resized.height).toBe(0.2); + }); + + it('should set zIndex', () => { + const decal = LiveryDecal.create({ + id: 'test-id', + imageUrl: 'http://example.com/image.png', + x: 0.5, + y: 0.5, + width: 0.2, + height: 0.1, + zIndex: 1, + type: 'sponsor', + }); + const updated = decal.setZIndex(5); + expect(updated.zIndex).toBe(5); + }); + + it('should rotate', () => { + const decal = LiveryDecal.create({ + id: 'test-id', + imageUrl: 'http://example.com/image.png', + x: 0.5, + y: 0.5, + width: 0.2, + height: 0.1, + zIndex: 1, + type: 'sponsor', + }); + const rotated = decal.rotate(90); + expect(rotated.rotation).toBe(90); + }); + + it('should normalize rotation', () => { + const decal = LiveryDecal.create({ + id: 'test-id', + imageUrl: 'http://example.com/image.png', + x: 0.5, + y: 0.5, + width: 0.2, + height: 0.1, + zIndex: 1, + type: 'sponsor', + }); + const rotated = decal.rotate(450); + expect(rotated.rotation).toBe(90); + }); + + it('should check overlaps', () => { + const decal1 = LiveryDecal.create({ + id: '1', + imageUrl: 'http://example.com/image.png', + x: 0.1, + y: 0.1, + width: 0.3, + height: 0.3, + zIndex: 1, + type: 'sponsor', + }); + const decal2 = LiveryDecal.create({ + id: '2', + imageUrl: 'http://example.com/image.png', + x: 0.2, + y: 0.2, + width: 0.3, + height: 0.3, + zIndex: 1, + type: 'sponsor', + }); + expect(decal1.overlapsWith(decal2)).toBe(true); + }); + + it('should not overlap when separate', () => { + const decal1 = LiveryDecal.create({ + id: '1', + imageUrl: 'http://example.com/image.png', + x: 0.1, + y: 0.1, + width: 0.2, + height: 0.2, + zIndex: 1, + type: 'sponsor', + }); + const decal2 = LiveryDecal.create({ + id: '2', + imageUrl: 'http://example.com/image.png', + x: 0.5, + y: 0.5, + width: 0.2, + height: 0.2, + zIndex: 1, + type: 'sponsor', + }); + expect(decal1.overlapsWith(decal2)).toBe(false); + }); + + it('should have props', () => { + const decal = LiveryDecal.create({ + id: 'test-id', + imageUrl: 'http://example.com/image.png', + x: 0.5, + y: 0.5, + width: 0.2, + height: 0.1, + zIndex: 1, + type: 'sponsor', + }); + expect(decal.props).toEqual({ + id: 'test-id', + imageUrl: 'http://example.com/image.png', + x: 0.5, + y: 0.5, + width: 0.2, + height: 0.1, + rotation: 0, + zIndex: 1, + type: 'sponsor', + }); + }); + + it('equals', () => { + const decal1 = LiveryDecal.create({ + id: 'test-id', + imageUrl: 'http://example.com/image.png', + x: 0.5, + y: 0.5, + width: 0.2, + height: 0.1, + zIndex: 1, + type: 'sponsor', + }); + const decal2 = LiveryDecal.create({ + id: 'test-id', + imageUrl: 'http://example.com/image.png', + x: 0.5, + y: 0.5, + width: 0.2, + height: 0.1, + zIndex: 1, + type: 'sponsor', + }); + const decal3 = LiveryDecal.create({ + id: 'different', + imageUrl: 'http://example.com/image.png', + x: 0.5, + y: 0.5, + width: 0.2, + height: 0.1, + zIndex: 1, + type: 'sponsor', + }); + expect(decal1.equals(decal2)).toBe(true); + expect(decal1.equals(decal3)).toBe(false); + }); +}); \ No newline at end of file diff --git a/core/racing/domain/value-objects/LiveryDecal.ts b/core/racing/domain/value-objects/LiveryDecal.ts index a55944fe5..2614c194c 100644 --- a/core/racing/domain/value-objects/LiveryDecal.ts +++ b/core/racing/domain/value-objects/LiveryDecal.ts @@ -123,25 +123,18 @@ export class LiveryDecal implements IValueObject { } /** - * Rotate decal - */ - rotate(rotation: number): LiveryDecal { - // Normalize rotation to 0-360 range - const normalizedRotation = ((rotation % 360) + 360) % 360; - return LiveryDecal.create({ - ...this, - rotation: normalizedRotation, - }); - } + * Rotate decal + */ + rotate(rotation: number): LiveryDecal { + // Normalize rotation to 0-360 range + const normalizedRotation = ((rotation % 360) + 360) % 360; + return LiveryDecal.create({ + ...this, + rotation: normalizedRotation, + }); + } - /** - * Get CSS transform string for rendering - */ - getCssTransform(): string { - return `rotate(${this.rotation}deg)`; - } - - get props(): LiveryDecalProps { + get props(): LiveryDecalProps { return { id: this.id, imageUrl: this.imageUrl, diff --git a/core/racing/domain/value-objects/MembershipFee.test.ts b/core/racing/domain/value-objects/MembershipFee.test.ts new file mode 100644 index 000000000..f479c7084 --- /dev/null +++ b/core/racing/domain/value-objects/MembershipFee.test.ts @@ -0,0 +1,73 @@ +import { describe, it, expect } from 'vitest'; +import { MembershipFee } from './MembershipFee'; +import { Money } from './Money'; + +describe('MembershipFee', () => { + it('should create valid membership fee', () => { + const amount = Money.create(1000, 'USD'); + const fee = MembershipFee.create('season', amount); + expect(fee.type).toBe('season'); + expect(fee.amount).toBe(amount); + }); + + it('should validate type required', () => { + const amount = Money.create(1000, 'USD'); + expect(() => MembershipFee.create('' as 'season', amount)).toThrow('MembershipFee type is required'); + }); + + it('should validate amount required', () => { + expect(() => MembershipFee.create('season', null as unknown as Money)).toThrow('MembershipFee amount is required'); + }); + + it('should validate amount not negative', () => { + expect(() => Money.create(-100, 'USD')).toThrow('Money amount cannot be negative'); + }); + + it('should get platform fee', () => { + const amount = Money.create(1000, 'USD'); // $10.00 + const fee = MembershipFee.create('season', amount); + const platformFee = fee.getPlatformFee(); + expect(platformFee.amount).toBe(100); // $1.00 + expect(platformFee.currency).toBe('USD'); + }); + + it('should get net amount', () => { + const amount = Money.create(1000, 'USD'); // $10.00 + const fee = MembershipFee.create('season', amount); + const netAmount = fee.getNetAmount(); + expect(netAmount.amount).toBe(900); // $9.00 + expect(netAmount.currency).toBe('USD'); + }); + + it('should check if recurring', () => { + const amount = Money.create(1000, 'USD'); + const seasonFee = MembershipFee.create('season', amount); + const monthlyFee = MembershipFee.create('monthly', amount); + const perRaceFee = MembershipFee.create('per_race', amount); + expect(seasonFee.isRecurring()).toBe(false); + expect(monthlyFee.isRecurring()).toBe(true); + expect(perRaceFee.isRecurring()).toBe(false); + }); + + it('should have props', () => { + const amount = Money.create(1000, 'USD'); + const fee = MembershipFee.create('season', amount); + expect(fee.props).toEqual({ + type: 'season', + amount, + }); + }); + + it('equals', () => { + const amount1 = Money.create(1000, 'USD'); + const amount2 = Money.create(1000, 'USD'); + const amount3 = Money.create(2000, 'USD'); + const fee1 = MembershipFee.create('season', amount1); + const fee2 = MembershipFee.create('season', amount2); + const fee3 = MembershipFee.create('monthly', amount1); + const fee4 = MembershipFee.create('season', amount3); + expect(fee1.equals(fee2)).toBe(true); + expect(fee1.equals(fee3)).toBe(false); + expect(fee1.equals(fee4)).toBe(false); + }); +}); \ No newline at end of file diff --git a/core/racing/domain/value-objects/MembershipFee.ts b/core/racing/domain/value-objects/MembershipFee.ts index e2526b40c..e881614e4 100644 --- a/core/racing/domain/value-objects/MembershipFee.ts +++ b/core/racing/domain/value-objects/MembershipFee.ts @@ -33,10 +33,6 @@ export class MembershipFee implements IValueObject { throw new RacingDomainValidationError('MembershipFee amount is required'); } - if (amount.amount < 0) { - throw new RacingDomainValidationError('MembershipFee amount cannot be negative'); - } - return new MembershipFee({ type, amount }); } @@ -62,29 +58,15 @@ export class MembershipFee implements IValueObject { } /** - * Check if this is a recurring fee - */ - isRecurring(): boolean { - return this.type === 'monthly'; - } + * Check if this is a recurring fee + */ + isRecurring(): boolean { + return this.type === 'monthly'; + } - equals(other: IValueObject): boolean { - const a = this.props; - const b = other.props; - return a.type === b.type && a.amount.equals(b.amount); - } - - /** - * Get display name for fee type - */ - getDisplayName(): string { - switch (this.type) { - case 'season': - return 'Season Fee'; - case 'monthly': - return 'Monthly Subscription'; - case 'per_race': - return 'Per-Race Fee'; - } - } + equals(other: IValueObject): boolean { + const a = this.props; + const b = other.props; + return a.type === b.type && a.amount.equals(b.amount); + } } \ No newline at end of file diff --git a/core/racing/domain/value-objects/Money.test.ts b/core/racing/domain/value-objects/Money.test.ts new file mode 100644 index 000000000..e230b198a --- /dev/null +++ b/core/racing/domain/value-objects/Money.test.ts @@ -0,0 +1,103 @@ +import { describe, it, expect } from 'vitest'; +import { Money } from './Money'; + +describe('Money', () => { + it('should create valid money', () => { + const money = Money.create(1000, 'USD'); + expect(money.amount).toBe(1000); + expect(money.currency).toBe('USD'); + }); + + it('should default to USD', () => { + const money = Money.create(500); + expect(money.currency).toBe('USD'); + }); + + it('should validate amount not negative', () => { + expect(() => Money.create(-100, 'USD')).toThrow('Money amount cannot be negative'); + }); + + it('should validate amount finite', () => { + expect(() => Money.create(Infinity, 'USD')).toThrow('Money amount must be a finite number'); + expect(() => Money.create(NaN, 'USD')).toThrow('Money amount must be a finite number'); + }); + + it('should calculate platform fee', () => { + const money = Money.create(1000, 'USD'); // $10.00 + const fee = money.calculatePlatformFee(); + expect(fee.amount).toBe(100); // $1.00 + expect(fee.currency).toBe('USD'); + }); + + it('should calculate net amount', () => { + const money = Money.create(1000, 'USD'); // $10.00 + const net = money.calculateNetAmount(); + expect(net.amount).toBe(900); // $9.00 + expect(net.currency).toBe('USD'); + }); + + it('should add money', () => { + const money1 = Money.create(500, 'USD'); + const money2 = Money.create(300, 'USD'); + const sum = money1.add(money2); + expect(sum.amount).toBe(800); + expect(sum.currency).toBe('USD'); + }); + + it('should not add different currencies', () => { + const money1 = Money.create(500, 'USD'); + const money2 = Money.create(300, 'EUR'); + expect(() => money1.add(money2)).toThrow('Cannot add money with different currencies'); + }); + + it('should subtract money', () => { + const money1 = Money.create(500, 'USD'); + const money2 = Money.create(300, 'USD'); + const diff = money1.subtract(money2); + expect(diff.amount).toBe(200); + expect(diff.currency).toBe('USD'); + }); + + it('should not subtract different currencies', () => { + const money1 = Money.create(500, 'USD'); + const money2 = Money.create(300, 'EUR'); + expect(() => money1.subtract(money2)).toThrow('Cannot subtract money with different currencies'); + }); + + it('should not subtract to negative', () => { + const money1 = Money.create(300, 'USD'); + const money2 = Money.create(500, 'USD'); + expect(() => money1.subtract(money2)).toThrow('Subtraction would result in negative amount'); + }); + + it('should check greater than', () => { + const money1 = Money.create(500, 'USD'); + const money2 = Money.create(300, 'USD'); + expect(money1.isGreaterThan(money2)).toBe(true); + expect(money2.isGreaterThan(money1)).toBe(false); + }); + + it('should not compare different currencies', () => { + const money1 = Money.create(500, 'USD'); + const money2 = Money.create(300, 'EUR'); + expect(() => money1.isGreaterThan(money2)).toThrow('Cannot compare money with different currencies'); + }); + + it('should have props', () => { + const money = Money.create(1000, 'USD'); + expect(money.props).toEqual({ + amount: 1000, + currency: 'USD', + }); + }); + + it('equals', () => { + const money1 = Money.create(1000, 'USD'); + const money2 = Money.create(1000, 'USD'); + const money3 = Money.create(500, 'USD'); + const money4 = Money.create(1000, 'EUR'); + expect(money1.equals(money2)).toBe(true); + expect(money1.equals(money3)).toBe(false); + expect(money1.equals(money4)).toBe(false); + }); +}); \ No newline at end of file diff --git a/core/racing/domain/value-objects/Money.ts b/core/racing/domain/value-objects/Money.ts index e322d22d9..88aa80dbd 100644 --- a/core/racing/domain/value-objects/Money.ts +++ b/core/racing/domain/value-objects/Money.ts @@ -92,24 +92,11 @@ export class Money implements IValueObject { } /** - * Check if this money equals another - */ - equals(other: IValueObject): boolean { - const a = this.props; - const b = other.props; - return a.amount === b.amount && a.currency === b.currency; - } - - /** - * Format money for display - */ - format(): string { - const formatter = new Intl.NumberFormat('en-US', { - style: 'currency', - currency: this.currency, - minimumFractionDigits: 2, - maximumFractionDigits: 2, - }); - return formatter.format(this.amount / 100); - } + * Check if this money equals another + */ + equals(other: IValueObject): boolean { + const a = this.props; + const b = other.props; + return a.amount === b.amount && a.currency === b.currency; + } } \ No newline at end of file diff --git a/core/racing/domain/value-objects/MonthlyRecurrencePattern.test.ts b/core/racing/domain/value-objects/MonthlyRecurrencePattern.test.ts new file mode 100644 index 000000000..634d542a6 --- /dev/null +++ b/core/racing/domain/value-objects/MonthlyRecurrencePattern.test.ts @@ -0,0 +1,53 @@ +import { describe, it, expect } from 'vitest'; +import { MonthlyRecurrencePattern } from './MonthlyRecurrencePattern'; + +describe('MonthlyRecurrencePattern', () => { + it('should create valid pattern', () => { + const pattern = MonthlyRecurrencePattern.create(1, 'Mon'); + expect(pattern.ordinal).toBe(1); + expect(pattern.weekday).toBe('Mon'); + }); + + it('should validate ordinal range', () => { + expect(() => MonthlyRecurrencePattern.create(0 as 1, 'Mon')).toThrow('MonthlyRecurrencePattern ordinal must be between 1 and 4'); + expect(() => MonthlyRecurrencePattern.create(5 as 1, 'Mon')).toThrow('MonthlyRecurrencePattern ordinal must be between 1 and 4'); + }); + + it('should validate weekday', () => { + expect(() => MonthlyRecurrencePattern.create(1, 'Invalid' as 'Mon')).toThrow('MonthlyRecurrencePattern weekday must be a valid weekday'); + }); + + it('should get ordinal suffix', () => { + const first = MonthlyRecurrencePattern.create(1, 'Mon'); + const second = MonthlyRecurrencePattern.create(2, 'Tue'); + const third = MonthlyRecurrencePattern.create(3, 'Wed'); + const fourth = MonthlyRecurrencePattern.create(4, 'Thu'); + expect(first.getOrdinalSuffix()).toBe('1st'); + expect(second.getOrdinalSuffix()).toBe('2nd'); + expect(third.getOrdinalSuffix()).toBe('3rd'); + expect(fourth.getOrdinalSuffix()).toBe('4th'); + }); + + it('should get description', () => { + const pattern = MonthlyRecurrencePattern.create(2, 'Wed'); + expect(pattern.getDescription()).toBe('2nd Wed of the month'); + }); + + it('should have props', () => { + const pattern = MonthlyRecurrencePattern.create(1, 'Mon'); + expect(pattern.props).toEqual({ + ordinal: 1, + weekday: 'Mon', + }); + }); + + it('equals', () => { + const pattern1 = MonthlyRecurrencePattern.create(1, 'Mon'); + const pattern2 = MonthlyRecurrencePattern.create(1, 'Mon'); + const pattern3 = MonthlyRecurrencePattern.create(2, 'Mon'); + const pattern4 = MonthlyRecurrencePattern.create(1, 'Tue'); + expect(pattern1.equals(pattern2)).toBe(true); + expect(pattern1.equals(pattern3)).toBe(false); + expect(pattern1.equals(pattern4)).toBe(false); + }); +}); \ No newline at end of file diff --git a/core/racing/domain/value-objects/MonthlyRecurrencePattern.ts b/core/racing/domain/value-objects/MonthlyRecurrencePattern.ts index e8287d62b..7d9eb5d87 100644 --- a/core/racing/domain/value-objects/MonthlyRecurrencePattern.ts +++ b/core/racing/domain/value-objects/MonthlyRecurrencePattern.ts @@ -1,3 +1,5 @@ +import { RacingDomainValidationError } from '../errors/RacingDomainError'; +import { ALL_WEEKDAYS } from '../types/Weekday'; import type { Weekday } from '../types/Weekday'; import type { IValueObject } from '@core/shared/domain'; @@ -9,20 +11,43 @@ export interface MonthlyRecurrencePatternProps { export class MonthlyRecurrencePattern implements IValueObject { readonly ordinal: 1 | 2 | 3 | 4; readonly weekday: Weekday; - - constructor(ordinal: 1 | 2 | 3 | 4, weekday: Weekday); - constructor(props: MonthlyRecurrencePatternProps); - constructor( - ordinalOrProps: 1 | 2 | 3 | 4 | MonthlyRecurrencePatternProps, - weekday?: Weekday, - ) { - if (typeof ordinalOrProps === 'object') { - this.ordinal = ordinalOrProps.ordinal; - this.weekday = ordinalOrProps.weekday; - } else { - this.ordinal = ordinalOrProps; - this.weekday = weekday as Weekday; + + private constructor(ordinal: 1 | 2 | 3 | 4, weekday: Weekday) { + this.ordinal = ordinal; + this.weekday = weekday; + } + + static create(ordinal: 1 | 2 | 3 | 4, weekday: Weekday): MonthlyRecurrencePattern { + if (!ordinal || ordinal < 1 || ordinal > 4) { + throw new RacingDomainValidationError('MonthlyRecurrencePattern ordinal must be between 1 and 4'); } + if (!weekday || !ALL_WEEKDAYS.includes(weekday)) { + throw new RacingDomainValidationError('MonthlyRecurrencePattern weekday must be a valid weekday'); + } + return new MonthlyRecurrencePattern(ordinal, weekday); + } + + /** + * Get the ordinal suffix (1st, 2nd, 3rd, 4th) + */ + getOrdinalSuffix(): string { + switch (this.ordinal) { + case 1: + return '1st'; + case 2: + return '2nd'; + case 3: + return '3rd'; + case 4: + return '4th'; + } + } + + /** + * Get description of the pattern + */ + getDescription(): string { + return `${this.getOrdinalSuffix()} ${this.weekday} of the month`; } get props(): MonthlyRecurrencePatternProps { diff --git a/core/racing/domain/value-objects/Points.ts b/core/racing/domain/value-objects/Points.ts index ea893953d..da93c7cab 100644 --- a/core/racing/domain/value-objects/Points.ts +++ b/core/racing/domain/value-objects/Points.ts @@ -1,8 +1,13 @@ +import type { IValueObject } from '@core/shared/domain'; import { RacingDomainValidationError } from '../errors/RacingDomainError'; -export class Points { +export class Points implements IValueObject<{ value: number }> { private constructor(private readonly value: number) {} + get props(): { value: number } { + return { value: this.value }; + } + static create(value: number): Points { if (value < 0) { throw new RacingDomainValidationError('Points cannot be negative'); @@ -14,7 +19,7 @@ export class Points { return this.value; } - equals(other: Points): boolean { - return this.value === other.value; + equals(other: IValueObject<{ value: number }>): boolean { + return this.value === other.props.value; } } \ No newline at end of file diff --git a/core/racing/domain/value-objects/PointsTable.test.ts b/core/racing/domain/value-objects/PointsTable.test.ts new file mode 100644 index 000000000..7a226bc5e --- /dev/null +++ b/core/racing/domain/value-objects/PointsTable.test.ts @@ -0,0 +1,45 @@ +import { describe, it, expect } from 'vitest'; +import { PointsTable } from './PointsTable'; + +describe('PointsTable', () => { + it('should create points table from record', () => { + const table = new PointsTable({ 1: 25, 2: 18, 3: 15 }); + expect(table.getPointsForPosition(1)).toBe(25); + expect(table.getPointsForPosition(2)).toBe(18); + expect(table.getPointsForPosition(3)).toBe(15); + }); + + it('should create points table from map', () => { + const map = new Map([[1, 25], [2, 18]]); + const table = new PointsTable(map); + expect(table.getPointsForPosition(1)).toBe(25); + expect(table.getPointsForPosition(2)).toBe(18); + }); + + it('should return 0 for invalid positions', () => { + const table = new PointsTable({ 1: 25 }); + expect(table.getPointsForPosition(0)).toBe(0); + expect(table.getPointsForPosition(-1)).toBe(0); + expect(table.getPointsForPosition(1.5)).toBe(0); + expect(table.getPointsForPosition(2)).toBe(0); + }); + + + it('should equal same points table', () => { + const t1 = new PointsTable({ 1: 25, 2: 18 }); + const t2 = new PointsTable({ 1: 25, 2: 18 }); + expect(t1.equals(t2)).toBe(true); + }); + + it('should not equal different points table', () => { + const t1 = new PointsTable({ 1: 25, 2: 18 }); + const t2 = new PointsTable({ 1: 25, 2: 19 }); + expect(t1.equals(t2)).toBe(false); + }); + + it('should not equal table with different size', () => { + const t1 = new PointsTable({ 1: 25 }); + const t2 = new PointsTable({ 1: 25, 2: 18 }); + expect(t1.equals(t2)).toBe(false); + }); +}); \ No newline at end of file diff --git a/core/racing/domain/value-objects/PointsTable.ts b/core/racing/domain/value-objects/PointsTable.ts index 0a241ad96..b8b6f7624 100644 --- a/core/racing/domain/value-objects/PointsTable.ts +++ b/core/racing/domain/value-objects/PointsTable.ts @@ -1,11 +1,11 @@ import type { IValueObject } from '@core/shared/domain'; export interface PointsTableProps { - pointsByPosition: Map; + pointsByPosition: ReadonlyMap; } export class PointsTable implements IValueObject { - private readonly pointsByPosition: Map; + private readonly pointsByPosition: ReadonlyMap; constructor(pointsByPosition: Record | Map) { if (pointsByPosition instanceof Map) { @@ -27,7 +27,7 @@ export class PointsTable implements IValueObject { get props(): PointsTableProps { return { - pointsByPosition: new Map(this.pointsByPosition), + pointsByPosition: this.pointsByPosition, }; } diff --git a/core/racing/domain/value-objects/RaceIncidents.test.ts b/core/racing/domain/value-objects/RaceIncidents.test.ts new file mode 100644 index 000000000..0341b0e19 --- /dev/null +++ b/core/racing/domain/value-objects/RaceIncidents.test.ts @@ -0,0 +1,102 @@ +import { describe, it, expect } from 'vitest'; +import { RaceIncidents, type IncidentRecord } from './RaceIncidents'; + +describe('RaceIncidents', () => { + const sampleIncident: IncidentRecord = { + type: 'contact', + lap: 5, + description: 'Minor contact', + penaltyPoints: 2, + }; + + const anotherIncident: IncidentRecord = { + type: 'track_limits', + lap: 10, + penaltyPoints: 0, + }; + + it('should create empty incidents', () => { + const incidents = new RaceIncidents(); + expect(incidents.getTotalCount()).toBe(0); + expect(incidents.isClean()).toBe(true); + expect(incidents.hasIncidents()).toBe(false); + }); + + it('should create incidents from array', () => { + const incidents = new RaceIncidents([sampleIncident]); + expect(incidents.getTotalCount()).toBe(1); + expect(incidents.isClean()).toBe(false); + expect(incidents.hasIncidents()).toBe(true); + }); + + it('should add incident immutably', () => { + const original = new RaceIncidents([sampleIncident]); + const updated = original.addIncident(anotherIncident); + expect(original.getTotalCount()).toBe(1); + expect(updated.getTotalCount()).toBe(2); + }); + + it('should get all incidents', () => { + const incidents = new RaceIncidents([sampleIncident, anotherIncident]); + const all = incidents.getAllIncidents(); + expect(all).toHaveLength(2); + expect(all).toEqual([sampleIncident, anotherIncident]); + }); + + it('should get total count', () => { + const incidents = new RaceIncidents([sampleIncident, anotherIncident]); + expect(incidents.getTotalCount()).toBe(2); + }); + + it('should get total penalty points', () => { + const incidents = new RaceIncidents([sampleIncident, anotherIncident]); + expect(incidents.getTotalPenaltyPoints()).toBe(2); + }); + + it('should get incidents by type', () => { + const incidents = new RaceIncidents([sampleIncident, anotherIncident]); + const contacts = incidents.getIncidentsByType('contact'); + expect(contacts).toHaveLength(1); + expect(contacts[0]).toEqual(sampleIncident); + }); + + it('should check has incidents', () => { + const empty = new RaceIncidents(); + const withIncidents = new RaceIncidents([sampleIncident]); + expect(empty.hasIncidents()).toBe(false); + expect(withIncidents.hasIncidents()).toBe(true); + }); + + it('should check is clean', () => { + const empty = new RaceIncidents(); + const withIncidents = new RaceIncidents([sampleIncident]); + expect(empty.isClean()).toBe(true); + expect(withIncidents.isClean()).toBe(false); + }); + + it('should equal same incidents', () => { + const i1 = new RaceIncidents([sampleIncident]); + const i2 = new RaceIncidents([sampleIncident]); + expect(i1.equals(i2)).toBe(true); + }); + + it('should not equal different incidents', () => { + const i1 = new RaceIncidents([sampleIncident]); + const i2 = new RaceIncidents([anotherIncident]); + expect(i1.equals(i2)).toBe(false); + }); + + it('should not equal different length', () => { + const i1 = new RaceIncidents([sampleIncident]); + const i2 = new RaceIncidents([sampleIncident, anotherIncident]); + expect(i1.equals(i2)).toBe(false); + }); + + it('should return props as copy', () => { + const incidents = new RaceIncidents([sampleIncident]); + const props = incidents.props; + expect(props).toEqual([sampleIncident]); + props.push(anotherIncident); + expect(incidents.getTotalCount()).toBe(1); + }); +}); \ No newline at end of file diff --git a/core/racing/domain/value-objects/RaceIncidents.ts b/core/racing/domain/value-objects/RaceIncidents.ts index a3511cef7..e81cc8299 100644 --- a/core/racing/domain/value-objects/RaceIncidents.ts +++ b/core/racing/domain/value-objects/RaceIncidents.ts @@ -90,70 +90,7 @@ export class RaceIncidents implements IValueObject { return this.incidents.length === 0; } - /** - * Get incident severity score (0-100, higher = more severe) - */ - getSeverityScore(): number { - if (this.incidents.length === 0) return 0; - - const severityWeights: Record = { - track_limits: 10, - contact: 20, - unsafe_rejoin: 25, - aggressive_driving: 15, - false_start: 30, - collision: 40, - spin: 35, - mechanical: 5, // Lower weight as it's not driver error - other: 15, - }; - - const totalSeverity = this.incidents.reduce((total, incident) => { - return total + severityWeights[incident.type]; - }, 0); - - // Normalize to 0-100 scale (cap at 100 for very incident-heavy races) - return Math.min(100, totalSeverity); - } - - /** - * Get human-readable incident summary - */ - getSummary(): string { - if (this.incidents.length === 0) { - return 'Clean race'; - } - - const typeCounts = this.incidents.reduce((counts, incident) => { - counts[incident.type] = (counts[incident.type] || 0) + 1; - return counts; - }, {} as Record); - - const summaryParts = Object.entries(typeCounts).map(([type, count]) => { - const typeLabel = this.getIncidentTypeLabel(type as IncidentType); - return count > 1 ? `${count}x ${typeLabel}` : typeLabel; - }); - - return summaryParts.join(', '); - } - - /** - * Get human-readable label for incident type - */ - private getIncidentTypeLabel(type: IncidentType): string { - const labels: Record = { - track_limits: 'Track Limits', - contact: 'Contact', - unsafe_rejoin: 'Unsafe Rejoin', - aggressive_driving: 'Aggressive Driving', - false_start: 'False Start', - collision: 'Collision', - spin: 'Spin', - mechanical: 'Mechanical', - other: 'Other', - }; - return labels[type]; - } + // Removed getSeverityScore, getSummary, and getIncidentTypeLabel to eliminate static data in core equals(other: IValueObject): boolean { const otherIncidents = other.props; @@ -174,66 +111,4 @@ export class RaceIncidents implements IValueObject { }); } - /** - * Create RaceIncidents from legacy incidents count - */ - static fromLegacyIncidentsCount(count: number): RaceIncidents { - if (count === 0) { - return new RaceIncidents(); - } - - // Distribute legacy incidents across different types based on probability - const incidents: IncidentRecord[] = []; - for (let i = 0; i < count; i++) { - const type = RaceIncidents.getRandomIncidentType(); - incidents.push({ - type, - lap: Math.floor(Math.random() * 20) + 1, // Random lap 1-20 - penaltyPoints: RaceIncidents.getDefaultPenaltyPoints(type), - }); - } - - return new RaceIncidents(incidents); - } - - /** - * Get random incident type for legacy data conversion - */ - private static getRandomIncidentType(): IncidentType { - const types: IncidentType[] = [ - 'track_limits', 'contact', 'unsafe_rejoin', 'aggressive_driving', - 'collision', 'spin', 'other' - ]; - const weights = [0.4, 0.25, 0.15, 0.1, 0.05, 0.04, 0.01]; // Probability weights - - const random = Math.random(); - let cumulativeWeight = 0; - - for (let i = 0; i < types.length; i++) { - cumulativeWeight += weights[i]; - if (random <= cumulativeWeight) { - return types[i]; - } - } - - return 'other'; - } - - /** - * Get default penalty points for incident type - */ - private static getDefaultPenaltyPoints(type: IncidentType): number { - const penalties: Record = { - track_limits: 0, // Usually just a warning - contact: 2, - unsafe_rejoin: 3, - aggressive_driving: 2, - false_start: 5, - collision: 5, - spin: 0, // Usually no penalty if no contact - mechanical: 0, - other: 2, - }; - return penalties[type]; - } } \ No newline at end of file diff --git a/core/racing/domain/value-objects/RaceTimeOfDay.test.ts b/core/racing/domain/value-objects/RaceTimeOfDay.test.ts new file mode 100644 index 000000000..685c35db0 --- /dev/null +++ b/core/racing/domain/value-objects/RaceTimeOfDay.test.ts @@ -0,0 +1,65 @@ +import { describe, it, expect } from 'vitest'; +import { RaceTimeOfDay } from './RaceTimeOfDay'; +import { RacingDomainValidationError } from '../errors/RacingDomainError'; + +describe('RaceTimeOfDay', () => { + it('should create valid time', () => { + const time = new RaceTimeOfDay(12, 30); + expect(time.hour).toBe(12); + expect(time.minute).toBe(30); + }); + + it('should throw on invalid hour', () => { + expect(() => new RaceTimeOfDay(24, 0)).toThrow(RacingDomainValidationError); + expect(() => new RaceTimeOfDay(-1, 0)).toThrow(RacingDomainValidationError); + expect(() => new RaceTimeOfDay(12.5, 0)).toThrow(RacingDomainValidationError); + }); + + it('should throw on invalid minute', () => { + expect(() => new RaceTimeOfDay(12, 60)).toThrow(RacingDomainValidationError); + expect(() => new RaceTimeOfDay(12, -1)).toThrow(RacingDomainValidationError); + expect(() => new RaceTimeOfDay(12, 30.5)).toThrow(RacingDomainValidationError); + }); + + it('should create from valid string', () => { + const time = RaceTimeOfDay.fromString('14:45'); + expect(time.hour).toBe(14); + expect(time.minute).toBe(45); + }); + + it('should throw on invalid string format', () => { + expect(() => RaceTimeOfDay.fromString('14:45:00')).toThrow(RacingDomainValidationError); + expect(() => RaceTimeOfDay.fromString('1445')).toThrow(RacingDomainValidationError); + expect(() => RaceTimeOfDay.fromString('14-45')).toThrow(RacingDomainValidationError); + }); + + it('should throw on invalid hour in string', () => { + expect(() => RaceTimeOfDay.fromString('25:00')).toThrow(RacingDomainValidationError); + }); + + it('should throw on invalid minute in string', () => { + expect(() => RaceTimeOfDay.fromString('12:60')).toThrow(RacingDomainValidationError); + }); + + it('should convert to string', () => { + const time = new RaceTimeOfDay(9, 5); + expect(time.toString()).toBe('09:05'); + }); + + it('should equal same time', () => { + const t1 = new RaceTimeOfDay(10, 20); + const t2 = new RaceTimeOfDay(10, 20); + expect(t1.equals(t2)).toBe(true); + }); + + it('should not equal different time', () => { + const t1 = new RaceTimeOfDay(10, 20); + const t2 = new RaceTimeOfDay(10, 21); + expect(t1.equals(t2)).toBe(false); + }); + + it('should return props', () => { + const time = new RaceTimeOfDay(15, 30); + expect(time.props).toEqual({ hour: 15, minute: 30 }); + }); +}); \ No newline at end of file diff --git a/core/racing/domain/value-objects/RecurrenceStrategy.test.ts b/core/racing/domain/value-objects/RecurrenceStrategy.test.ts new file mode 100644 index 000000000..301023d34 --- /dev/null +++ b/core/racing/domain/value-objects/RecurrenceStrategy.test.ts @@ -0,0 +1,107 @@ +import { describe, it, expect } from 'vitest'; +import { RecurrenceStrategy } from './RecurrenceStrategy'; +import { WeekdaySet } from './WeekdaySet'; +import { MonthlyRecurrencePattern } from './MonthlyRecurrencePattern'; + +describe('RecurrenceStrategy', () => { + describe('weekly', () => { + it('should create weekly recurrence', () => { + const weekdays = WeekdaySet.fromArray(['Mon', 'Wed', 'Fri']); + const strategy = RecurrenceStrategy.weekly(weekdays); + expect(strategy.props.kind).toBe('weekly'); + expect((strategy.props as { kind: 'weekly'; weekdays: WeekdaySet }).weekdays).toBe(weekdays); + }); + + it('should throw if no weekdays', () => { + expect(() => WeekdaySet.fromArray([])).toThrow('WeekdaySet requires at least one weekday'); + }); + }); + + describe('everyNWeeks', () => { + it('should create everyNWeeks recurrence', () => { + const weekdays = WeekdaySet.fromArray(['Tue', 'Thu']); + const strategy = RecurrenceStrategy.everyNWeeks(2, weekdays); + expect(strategy.props.kind).toBe('everyNWeeks'); + const props = strategy.props as { kind: 'everyNWeeks'; intervalWeeks: number; weekdays: WeekdaySet }; + expect(props.intervalWeeks).toBe(2); + expect(props.weekdays).toBe(weekdays); + }); + + it('should throw if intervalWeeks not positive integer', () => { + const weekdays = WeekdaySet.fromArray(['Mon']); + expect(() => RecurrenceStrategy.everyNWeeks(0, weekdays)).toThrow('intervalWeeks must be a positive integer'); + expect(() => RecurrenceStrategy.everyNWeeks(-1, weekdays)).toThrow('intervalWeeks must be a positive integer'); + expect(() => RecurrenceStrategy.everyNWeeks(1.5, weekdays)).toThrow('intervalWeeks must be a positive integer'); + }); + + it('should throw if no weekdays', () => { + expect(() => WeekdaySet.fromArray([])).toThrow('WeekdaySet requires at least one weekday'); + }); + }); + + describe('monthlyNthWeekday', () => { + it('should create monthlyNthWeekday recurrence', () => { + const pattern = MonthlyRecurrencePattern.create(1, 'Mon'); + const strategy = RecurrenceStrategy.monthlyNthWeekday(pattern); + expect(strategy.props.kind).toBe('monthlyNthWeekday'); + expect((strategy.props as { kind: 'monthlyNthWeekday'; monthlyPattern: MonthlyRecurrencePattern }).monthlyPattern).toBe(pattern); + }); + }); + + describe('equals', () => { + it('should equal same weekly strategies', () => { + const weekdays1 = WeekdaySet.fromArray(['Mon', 'Wed']); + const weekdays2 = WeekdaySet.fromArray(['Mon', 'Wed']); + const s1 = RecurrenceStrategy.weekly(weekdays1); + const s2 = RecurrenceStrategy.weekly(weekdays2); + expect(s1.equals(s2)).toBe(true); + }); + + it('should not equal different weekly strategies', () => { + const weekdays1 = WeekdaySet.fromArray(['Mon']); + const weekdays2 = WeekdaySet.fromArray(['Tue']); + const s1 = RecurrenceStrategy.weekly(weekdays1); + const s2 = RecurrenceStrategy.weekly(weekdays2); + expect(s1.equals(s2)).toBe(false); + }); + + it('should equal same everyNWeeks strategies', () => { + const weekdays1 = WeekdaySet.fromArray(['Fri']); + const weekdays2 = WeekdaySet.fromArray(['Fri']); + const s1 = RecurrenceStrategy.everyNWeeks(3, weekdays1); + const s2 = RecurrenceStrategy.everyNWeeks(3, weekdays2); + expect(s1.equals(s2)).toBe(true); + }); + + it('should not equal different everyNWeeks strategies', () => { + const weekdays = WeekdaySet.fromArray(['Sat']); + const s1 = RecurrenceStrategy.everyNWeeks(2, weekdays); + const s2 = RecurrenceStrategy.everyNWeeks(4, weekdays); + expect(s1.equals(s2)).toBe(false); + }); + + it('should equal same monthlyNthWeekday strategies', () => { + const pattern1 = MonthlyRecurrencePattern.create(2, 'Wed'); + const pattern2 = MonthlyRecurrencePattern.create(2, 'Wed'); + const s1 = RecurrenceStrategy.monthlyNthWeekday(pattern1); + const s2 = RecurrenceStrategy.monthlyNthWeekday(pattern2); + expect(s1.equals(s2)).toBe(true); + }); + + it('should not equal different monthlyNthWeekday strategies', () => { + const pattern1 = MonthlyRecurrencePattern.create(1, 'Thu'); + const pattern2 = MonthlyRecurrencePattern.create(3, 'Thu'); + const s1 = RecurrenceStrategy.monthlyNthWeekday(pattern1); + const s2 = RecurrenceStrategy.monthlyNthWeekday(pattern2); + expect(s1.equals(s2)).toBe(false); + }); + + it('should not equal different kinds', () => { + const weekdays = WeekdaySet.fromArray(['Sun']); + const pattern = MonthlyRecurrencePattern.create(4, 'Sun'); + const s1 = RecurrenceStrategy.weekly(weekdays); + const s2 = RecurrenceStrategy.monthlyNthWeekday(pattern); + expect(s1.equals(s2)).toBe(false); + }); + }); +}); \ No newline at end of file diff --git a/core/racing/domain/value-objects/RecurrenceStrategy.ts b/core/racing/domain/value-objects/RecurrenceStrategy.ts index 6a47cfd06..2c0add17e 100644 --- a/core/racing/domain/value-objects/RecurrenceStrategy.ts +++ b/core/racing/domain/value-objects/RecurrenceStrategy.ts @@ -1,38 +1,45 @@ +import type { IValueObject } from '@core/shared/domain'; import { WeekdaySet } from './WeekdaySet'; import { MonthlyRecurrencePattern } from './MonthlyRecurrencePattern'; import { RacingDomainValidationError } from '../errors/RacingDomainError'; -export type WeeklyRecurrenceStrategy = { +export type WeeklyRecurrenceStrategyProps = { kind: 'weekly'; weekdays: WeekdaySet; }; -export type EveryNWeeksRecurrenceStrategy = { +export type EveryNWeeksRecurrenceStrategyProps = { kind: 'everyNWeeks'; weekdays: WeekdaySet; intervalWeeks: number; }; -export type MonthlyNthWeekdayRecurrenceStrategy = { +export type MonthlyNthWeekdayRecurrenceStrategyProps = { kind: 'monthlyNthWeekday'; monthlyPattern: MonthlyRecurrencePattern; }; -export type RecurrenceStrategy = - | WeeklyRecurrenceStrategy - | EveryNWeeksRecurrenceStrategy - | MonthlyNthWeekdayRecurrenceStrategy; +export type RecurrenceStrategyProps = + | WeeklyRecurrenceStrategyProps + | EveryNWeeksRecurrenceStrategyProps + | MonthlyNthWeekdayRecurrenceStrategyProps; + +export class RecurrenceStrategy implements IValueObject { + private constructor(private readonly strategy: RecurrenceStrategyProps) {} + + get props(): RecurrenceStrategyProps { + return this.strategy; + } -export class RecurrenceStrategyFactory { static weekly(weekdays: WeekdaySet): RecurrenceStrategy { if (weekdays.getAll().length === 0) { throw new RacingDomainValidationError('weekdays are required for weekly recurrence'); } - return { + return new RecurrenceStrategy({ kind: 'weekly', weekdays, - }; + }); } static everyNWeeks(intervalWeeks: number, weekdays: WeekdaySet): RecurrenceStrategy { @@ -43,17 +50,36 @@ export class RecurrenceStrategyFactory { throw new RacingDomainValidationError('weekdays are required for everyNWeeks recurrence'); } - return { + return new RecurrenceStrategy({ kind: 'everyNWeeks', weekdays, intervalWeeks, - }; + }); } static monthlyNthWeekday(pattern: MonthlyRecurrencePattern): RecurrenceStrategy { - return { + return new RecurrenceStrategy({ kind: 'monthlyNthWeekday', monthlyPattern: pattern, - }; + }); + } + + equals(other: IValueObject): boolean { + const otherProps = other.props; + if (this.strategy.kind !== otherProps.kind) { + return false; + } + switch (this.strategy.kind) { + case 'weekly': + return this.strategy.weekdays.equals((otherProps as WeeklyRecurrenceStrategyProps).weekdays); + case 'everyNWeeks': + const everyN = otherProps as EveryNWeeksRecurrenceStrategyProps; + return this.strategy.intervalWeeks === everyN.intervalWeeks && this.strategy.weekdays.equals(everyN.weekdays); + case 'monthlyNthWeekday': + const monthly = otherProps as MonthlyNthWeekdayRecurrenceStrategyProps; + return this.strategy.monthlyPattern.equals(monthly.monthlyPattern); + default: + return false; + } } } \ No newline at end of file diff --git a/core/racing/domain/value-objects/ScheduledRaceSlot.test.ts b/core/racing/domain/value-objects/ScheduledRaceSlot.test.ts new file mode 100644 index 000000000..6f252197c --- /dev/null +++ b/core/racing/domain/value-objects/ScheduledRaceSlot.test.ts @@ -0,0 +1,65 @@ +import { describe, it, expect } from 'vitest'; +import { ScheduledRaceSlot } from './ScheduledRaceSlot'; +import { LeagueTimezone } from './LeagueTimezone'; + +describe('ScheduledRaceSlot', () => { + it('should create scheduled race slot', () => { + const timezone = LeagueTimezone.create('America/New_York'); + const scheduledAt = new Date('2023-10-01T12:00:00Z'); + const slot = new ScheduledRaceSlot({ + roundNumber: 1, + scheduledAt, + timezone, + }); + expect(slot.roundNumber).toBe(1); + expect(slot.scheduledAt).toBe(scheduledAt); + expect(slot.timezone).toBe(timezone); + }); + + it('should throw if roundNumber not positive integer', () => { + const timezone = LeagueTimezone.create('UTC'); + const scheduledAt = new Date(); + expect(() => new ScheduledRaceSlot({ roundNumber: 0, scheduledAt, timezone })).toThrow('ScheduledRaceSlot.roundNumber must be a positive integer'); + expect(() => new ScheduledRaceSlot({ roundNumber: -1, scheduledAt, timezone })).toThrow('ScheduledRaceSlot.roundNumber must be a positive integer'); + expect(() => new ScheduledRaceSlot({ roundNumber: 1.5, scheduledAt, timezone })).toThrow('ScheduledRaceSlot.roundNumber must be a positive integer'); + }); + + it('should throw if scheduledAt not valid Date', () => { + const timezone = LeagueTimezone.create('UTC'); + expect(() => new ScheduledRaceSlot({ roundNumber: 1, scheduledAt: new Date('invalid'), timezone })).toThrow('ScheduledRaceSlot.scheduledAt must be a valid Date'); + }); + + it('should equal same slots', () => { + const timezone1 = LeagueTimezone.create('Europe/London'); + const timezone2 = LeagueTimezone.create('Europe/London'); + const date1 = new Date('2023-05-15T10:00:00Z'); + const date2 = new Date('2023-05-15T10:00:00Z'); + const s1 = new ScheduledRaceSlot({ roundNumber: 2, scheduledAt: date1, timezone: timezone1 }); + const s2 = new ScheduledRaceSlot({ roundNumber: 2, scheduledAt: date2, timezone: timezone2 }); + expect(s1.equals(s2)).toBe(true); + }); + + it('should not equal different round numbers', () => { + const timezone = LeagueTimezone.create('UTC'); + const date = new Date(); + const s1 = new ScheduledRaceSlot({ roundNumber: 1, scheduledAt: date, timezone }); + const s2 = new ScheduledRaceSlot({ roundNumber: 2, scheduledAt: date, timezone }); + expect(s1.equals(s2)).toBe(false); + }); + + it('should not equal different dates', () => { + const timezone = LeagueTimezone.create('UTC'); + const s1 = new ScheduledRaceSlot({ roundNumber: 1, scheduledAt: new Date('2023-01-01'), timezone }); + const s2 = new ScheduledRaceSlot({ roundNumber: 1, scheduledAt: new Date('2023-01-02'), timezone }); + expect(s1.equals(s2)).toBe(false); + }); + + it('should not equal different timezones', () => { + const timezone1 = LeagueTimezone.create('UTC'); + const timezone2 = LeagueTimezone.create('EST'); + const date = new Date(); + const s1 = new ScheduledRaceSlot({ roundNumber: 1, scheduledAt: date, timezone: timezone1 }); + const s2 = new ScheduledRaceSlot({ roundNumber: 1, scheduledAt: date, timezone: timezone2 }); + expect(s1.equals(s2)).toBe(false); + }); +}); \ No newline at end of file diff --git a/core/racing/domain/value-objects/SeasonSchedule.test.ts b/core/racing/domain/value-objects/SeasonSchedule.test.ts new file mode 100644 index 000000000..32425d9cd --- /dev/null +++ b/core/racing/domain/value-objects/SeasonSchedule.test.ts @@ -0,0 +1,112 @@ +import { describe, it, expect } from 'vitest'; + +import { + RacingDomainValidationError, +} from '../errors/RacingDomainError'; + +import { SeasonSchedule } from './SeasonSchedule'; +import { RaceTimeOfDay } from './RaceTimeOfDay'; +import { LeagueTimezone } from './LeagueTimezone'; +import { RecurrenceStrategyFactory } from './RecurrenceStrategy'; +import { WeekdaySet } from './WeekdaySet'; +import { MonthlyRecurrencePattern } from './MonthlyRecurrencePattern'; + +describe('SeasonSchedule', () => { + const timeOfDay = RaceTimeOfDay.fromString('20:00'); + const timezone = LeagueTimezone.create('Europe/Berlin'); + const startDate = new Date('2024-01-01'); + + it('creates a valid schedule with weekly recurrence', () => { + const recurrence = RecurrenceStrategyFactory.weekly(WeekdaySet.fromArray(['Mon', 'Wed', 'Fri'])); + const schedule = new SeasonSchedule({ + startDate, + timeOfDay, + timezone, + recurrence, + plannedRounds: 10, + }); + + expect(schedule.startDate.getFullYear()).toBe(2024); + expect(schedule.startDate.getMonth()).toBe(0); // January is 0 + expect(schedule.startDate.getDate()).toBe(1); + expect(schedule.startDate.getHours()).toBe(0); + expect(schedule.startDate.getMinutes()).toBe(0); + expect(schedule.startDate.getSeconds()).toBe(0); + expect(schedule.timeOfDay.equals(timeOfDay)).toBe(true); + expect(schedule.timezone.equals(timezone)).toBe(true); + expect(schedule.recurrence).toEqual(recurrence); + expect(schedule.plannedRounds).toBe(10); + }); + + it('throws when startDate is invalid', () => { + const recurrence = RecurrenceStrategyFactory.weekly(WeekdaySet.fromArray(['Mon'])); + expect( + () => + new SeasonSchedule({ + startDate: new Date('invalid'), + timeOfDay, + timezone, + recurrence, + plannedRounds: 10, + }), + ).toThrow(RacingDomainValidationError); + }); + + it('throws when plannedRounds is not positive integer', () => { + const recurrence = RecurrenceStrategyFactory.weekly(WeekdaySet.fromArray(['Mon'])); + expect( + () => + new SeasonSchedule({ + startDate, + timeOfDay, + timezone, + recurrence, + plannedRounds: 0, + }), + ).toThrow(RacingDomainValidationError); + + expect( + () => + new SeasonSchedule({ + startDate, + timeOfDay, + timezone, + recurrence, + plannedRounds: -1, + }), + ).toThrow(RacingDomainValidationError); + }); + + it('equals compares all props', () => { + const recurrence1 = RecurrenceStrategyFactory.weekly(WeekdaySet.fromArray(['Mon', 'Wed'])); + const recurrence2 = RecurrenceStrategyFactory.weekly(WeekdaySet.fromArray(['Mon', 'Wed'])); + const recurrence3 = RecurrenceStrategyFactory.monthlyNthWeekday(MonthlyRecurrencePattern.create(1, 'Mon')); + + const a = new SeasonSchedule({ + startDate, + timeOfDay, + timezone, + recurrence: recurrence1, + plannedRounds: 10, + }); + + const b = new SeasonSchedule({ + startDate, + timeOfDay, + timezone, + recurrence: recurrence2, + plannedRounds: 10, + }); + + const c = new SeasonSchedule({ + startDate, + timeOfDay, + timezone, + recurrence: recurrence3, + plannedRounds: 10, + }); + + expect(a.equals(b)).toBe(true); + expect(a.equals(c)).toBe(false); + }); +}); \ No newline at end of file diff --git a/core/racing/domain/value-objects/SessionType.test.ts b/core/racing/domain/value-objects/SessionType.test.ts new file mode 100644 index 000000000..5ce1c4b3b --- /dev/null +++ b/core/racing/domain/value-objects/SessionType.test.ts @@ -0,0 +1,86 @@ +import { describe, it, expect } from 'vitest'; +import { SessionType } from './SessionType'; + +describe('SessionType', () => { + it('should create session type', () => { + const session = new SessionType('practice'); + expect(session.value).toBe('practice'); + expect(session.props).toBe('practice'); + }); + + it('should throw for invalid session type', () => { + expect(() => new SessionType('invalid' as any)).toThrow(); + expect(() => new SessionType('' as any)).toThrow(); + }); + + it('should have static factory methods', () => { + expect(SessionType.practice().value).toBe('practice'); + expect(SessionType.qualifying().value).toBe('qualifying'); + expect(SessionType.sprint().value).toBe('sprint'); + expect(SessionType.main().value).toBe('main'); + expect(SessionType.timeTrial().value).toBe('timeTrial'); + }); + + it('should equal same session types', () => { + const s1 = new SessionType('main'); + const s2 = new SessionType('main'); + expect(s1.equals(s2)).toBe(true); + }); + + it('should not equal different session types', () => { + const s1 = new SessionType('practice'); + const s2 = new SessionType('qualifying'); + expect(s1.equals(s2)).toBe(false); + }); + + describe('countsForPoints', () => { + it('should return true for main and sprint', () => { + expect(SessionType.main().countsForPoints()).toBe(true); + expect(SessionType.sprint().countsForPoints()).toBe(true); + }); + + it('should return false for others', () => { + expect(SessionType.practice().countsForPoints()).toBe(false); + expect(SessionType.qualifying().countsForPoints()).toBe(false); + expect(SessionType.timeTrial().countsForPoints()).toBe(false); + }); + }); + + describe('determinesGrid', () => { + it('should return true for qualifying and q sessions', () => { + expect(SessionType.qualifying().determinesGrid()).toBe(true); + expect(new SessionType('q1').determinesGrid()).toBe(true); + expect(new SessionType('q2').determinesGrid()).toBe(true); + expect(new SessionType('q3').determinesGrid()).toBe(true); + }); + + it('should return false for others', () => { + expect(SessionType.practice().determinesGrid()).toBe(false); + expect(SessionType.sprint().determinesGrid()).toBe(false); + expect(SessionType.main().determinesGrid()).toBe(false); + expect(SessionType.timeTrial().determinesGrid()).toBe(false); + }); + }); + + describe('getDisplayName', () => { + it('should return correct display names', () => { + expect(SessionType.practice().getDisplayName()).toBe('Practice'); + expect(SessionType.qualifying().getDisplayName()).toBe('Qualifying'); + expect(new SessionType('q1').getDisplayName()).toBe('Q1'); + expect(SessionType.sprint().getDisplayName()).toBe('Sprint Race'); + expect(SessionType.main().getDisplayName()).toBe('Main Race'); + expect(SessionType.timeTrial().getDisplayName()).toBe('Time Trial'); + }); + }); + + describe('getShortName', () => { + it('should return correct short names', () => { + expect(SessionType.practice().getShortName()).toBe('P'); + expect(SessionType.qualifying().getShortName()).toBe('Q'); + expect(new SessionType('q1').getShortName()).toBe('Q1'); + expect(SessionType.sprint().getShortName()).toBe('SPR'); + expect(SessionType.main().getShortName()).toBe('RACE'); + expect(SessionType.timeTrial().getShortName()).toBe('TT'); + }); + }); +}); \ No newline at end of file diff --git a/core/racing/domain/value-objects/SponsorshipPricing.test.ts b/core/racing/domain/value-objects/SponsorshipPricing.test.ts new file mode 100644 index 000000000..5dafbe150 --- /dev/null +++ b/core/racing/domain/value-objects/SponsorshipPricing.test.ts @@ -0,0 +1,121 @@ +import { describe, it, expect } from 'vitest'; +import { SponsorshipPricing } from './SponsorshipPricing'; +import { Money } from './Money'; + +describe('SponsorshipPricing', () => { + const mainSlot = { + tier: 'main' as const, + price: Money.create(100, 'USD'), + benefits: ['Logo placement'], + available: true, + maxSlots: 1, + }; + + const secondarySlot = { + tier: 'secondary' as const, + price: Money.create(50, 'USD'), + benefits: ['Minor placement'], + available: true, + maxSlots: 2, + }; + + it('should create sponsorship pricing', () => { + const pricing = SponsorshipPricing.create({ + mainSlot, + acceptingApplications: true, + }); + expect(pricing.mainSlot).toEqual(mainSlot); + expect(pricing.acceptingApplications).toBe(true); + }); + + it('should create with defaults', () => { + const pricing = SponsorshipPricing.create(); + expect(pricing.acceptingApplications).toBe(true); + expect(pricing.mainSlot).toBeUndefined(); + }); + + it('should equal same pricings', () => { + const p1 = SponsorshipPricing.create({ mainSlot, acceptingApplications: true }); + const p2 = SponsorshipPricing.create({ mainSlot, acceptingApplications: true }); + expect(p1.equals(p2)).toBe(true); + }); + + it('should not equal different pricings', () => { + const p1 = SponsorshipPricing.create({ mainSlot, acceptingApplications: true }); + const p2 = SponsorshipPricing.create({ mainSlot, acceptingApplications: false }); + expect(p1.equals(p2)).toBe(false); + }); + + describe('isSlotAvailable', () => { + it('should return availability for main slot', () => { + const pricing = SponsorshipPricing.create({ mainSlot }); + expect(pricing.isSlotAvailable('main')).toBe(true); + }); + + it('should return availability for secondary slot', () => { + const pricing = SponsorshipPricing.create({ secondarySlots: secondarySlot }); + expect(pricing.isSlotAvailable('secondary')).toBe(true); + }); + + it('should return false for unavailable main slot', () => { + const unavailableMain = { ...mainSlot, available: false }; + const pricing = SponsorshipPricing.create({ mainSlot: unavailableMain }); + expect(pricing.isSlotAvailable('main')).toBe(false); + }); + + it('should return false for undefined main slot', () => { + const pricing = SponsorshipPricing.create(); + expect(pricing.isSlotAvailable('main')).toBe(false); + }); + }); + + describe('getPrice', () => { + it('should return price for main slot', () => { + const pricing = SponsorshipPricing.create({ mainSlot }); + expect(pricing.getPrice('main')).toEqual(mainSlot.price); + }); + + it('should return null for undefined main slot', () => { + const pricing = SponsorshipPricing.create(); + expect(pricing.getPrice('main')).toBeNull(); + }); + }); + + describe('getBenefits', () => { + it('should return benefits for main slot', () => { + const pricing = SponsorshipPricing.create({ mainSlot }); + expect(pricing.getBenefits('main')).toEqual(mainSlot.benefits); + }); + + it('should return empty array for undefined main slot', () => { + const pricing = SponsorshipPricing.create(); + expect(pricing.getBenefits('main')).toEqual([]); + }); + }); + + describe('updateMainSlot', () => { + it('should update main slot', () => { + const pricing = SponsorshipPricing.create(); + const updated = pricing.updateMainSlot({ price: Money.create(200, 'USD') }); + expect(updated.mainSlot?.price).toEqual(Money.create(200, 'USD')); + expect(updated.mainSlot?.tier).toBe('main'); + }); + }); + + describe('updateSecondarySlot', () => { + it('should update secondary slot', () => { + const pricing = SponsorshipPricing.create(); + const updated = pricing.updateSecondarySlot({ price: Money.create(75, 'USD') }); + expect(updated.secondarySlots?.price).toEqual(Money.create(75, 'USD')); + expect(updated.secondarySlots?.tier).toBe('secondary'); + }); + }); + + describe('setAcceptingApplications', () => { + it('should set accepting applications', () => { + const pricing = SponsorshipPricing.create({ acceptingApplications: true }); + const updated = pricing.setAcceptingApplications(false); + expect(updated.acceptingApplications).toBe(false); + }); + }); +}); \ No newline at end of file diff --git a/core/racing/domain/value-objects/SponsorshipPricing.ts b/core/racing/domain/value-objects/SponsorshipPricing.ts index 08edb171c..6d381741e 100644 --- a/core/racing/domain/value-objects/SponsorshipPricing.ts +++ b/core/racing/domain/value-objects/SponsorshipPricing.ts @@ -92,83 +92,6 @@ export class SponsorshipPricing implements IValueObject }); } - /** - * Create default pricing for a driver - */ - static defaultDriver(): SponsorshipPricing { - return new SponsorshipPricing({ - mainSlot: { - tier: 'main', - price: Money.create(200, 'USD'), - benefits: ['Suit logo', 'Helmet branding', 'Social mentions'], - available: true, - maxSlots: 1, - }, - acceptingApplications: true, - }); - } - - /** - * Create default pricing for a team - */ - static defaultTeam(): SponsorshipPricing { - return new SponsorshipPricing({ - mainSlot: { - tier: 'main', - price: Money.create(500, 'USD'), - benefits: ['Team name suffix', 'Car livery', 'All driver suits'], - available: true, - maxSlots: 1, - }, - secondarySlots: { - tier: 'secondary', - price: Money.create(250, 'USD'), - benefits: ['Team page logo', 'Minor livery placement'], - available: true, - maxSlots: 2, - }, - acceptingApplications: true, - }); - } - - /** - * Create default pricing for a race - */ - static defaultRace(): SponsorshipPricing { - return new SponsorshipPricing({ - mainSlot: { - tier: 'main', - price: Money.create(300, 'USD'), - benefits: ['Race title sponsor', 'Stream overlay', 'Results banner'], - available: true, - maxSlots: 1, - }, - acceptingApplications: true, - }); - } - - /** - * Create default pricing for a league/season - */ - static defaultLeague(): SponsorshipPricing { - return new SponsorshipPricing({ - mainSlot: { - tier: 'main', - price: Money.create(800, 'USD'), - benefits: ['Hood placement', 'League banner', 'Prominent logo', 'League page URL'], - available: true, - maxSlots: 1, - }, - secondarySlots: { - tier: 'secondary', - price: Money.create(250, 'USD'), - benefits: ['Side logo placement', 'League page listing'], - available: true, - maxSlots: 2, - }, - acceptingApplications: true, - }); - } /** * Check if a specific tier is available diff --git a/core/racing/domain/value-objects/TeamCreatedAt.test.ts b/core/racing/domain/value-objects/TeamCreatedAt.test.ts new file mode 100644 index 000000000..31e936146 --- /dev/null +++ b/core/racing/domain/value-objects/TeamCreatedAt.test.ts @@ -0,0 +1,31 @@ +import { describe, it, expect } from 'vitest'; +import { TeamCreatedAt } from './TeamCreatedAt'; + +describe('TeamCreatedAt', () => { + it('should create team created at', () => { + const date = new Date('2023-01-01'); + const created = TeamCreatedAt.create(date); + expect(created.toDate()).toEqual(date); + expect(created.props).toEqual(date); + }); + + it('should throw for future date', () => { + const future = new Date(); + future.setFullYear(future.getFullYear() + 1); + expect(() => TeamCreatedAt.create(future)).toThrow('Created date cannot be in the future'); + }); + + it('should equal same dates', () => { + const date1 = new Date('2023-01-01'); + const date2 = new Date('2023-01-01'); + const c1 = TeamCreatedAt.create(date1); + const c2 = TeamCreatedAt.create(date2); + expect(c1.equals(c2)).toBe(true); + }); + + it('should not equal different dates', () => { + const c1 = TeamCreatedAt.create(new Date('2023-01-01')); + const c2 = TeamCreatedAt.create(new Date('2023-01-02')); + expect(c1.equals(c2)).toBe(false); + }); +}); \ No newline at end of file diff --git a/core/racing/domain/value-objects/TeamCreatedAt.ts b/core/racing/domain/value-objects/TeamCreatedAt.ts index 73e56dca1..bdab6f0ab 100644 --- a/core/racing/domain/value-objects/TeamCreatedAt.ts +++ b/core/racing/domain/value-objects/TeamCreatedAt.ts @@ -1,8 +1,13 @@ import { RacingDomainValidationError } from '../errors/RacingDomainError'; +import type { IValueObject } from '@core/shared/domain'; -export class TeamCreatedAt { +export class TeamCreatedAt implements IValueObject { private constructor(private readonly value: Date) {} + get props(): Date { + return new Date(this.value); + } + static create(value: Date): TeamCreatedAt { const now = new Date(); if (value > now) { @@ -15,7 +20,7 @@ export class TeamCreatedAt { return new Date(this.value); } - equals(other: TeamCreatedAt): boolean { - return this.value.getTime() === other.value.getTime(); + equals(other: IValueObject): boolean { + return this.value.getTime() === other.props.getTime(); } } \ No newline at end of file diff --git a/core/racing/domain/value-objects/TeamDescription.test.ts b/core/racing/domain/value-objects/TeamDescription.test.ts new file mode 100644 index 000000000..7795961e2 --- /dev/null +++ b/core/racing/domain/value-objects/TeamDescription.test.ts @@ -0,0 +1,32 @@ +import { describe, it, expect } from 'vitest'; +import { TeamDescription } from './TeamDescription'; + +describe('TeamDescription', () => { + it('should create team description', () => { + const desc = TeamDescription.create('A great team'); + expect(desc.toString()).toBe('A great team'); + expect(desc.props).toBe('A great team'); + }); + + it('should trim whitespace', () => { + const desc = TeamDescription.create(' Description '); + expect(desc.toString()).toBe('Description'); + }); + + it('should throw for empty description', () => { + expect(() => TeamDescription.create('')).toThrow('Team description is required'); + expect(() => TeamDescription.create(' ')).toThrow('Team description is required'); + }); + + it('should equal same descriptions', () => { + const d1 = TeamDescription.create('Desc A'); + const d2 = TeamDescription.create('Desc A'); + expect(d1.equals(d2)).toBe(true); + }); + + it('should not equal different descriptions', () => { + const d1 = TeamDescription.create('Desc A'); + const d2 = TeamDescription.create('Desc B'); + expect(d1.equals(d2)).toBe(false); + }); +}); \ No newline at end of file diff --git a/core/racing/domain/value-objects/TeamDescription.ts b/core/racing/domain/value-objects/TeamDescription.ts index 44be11612..e0369d5b7 100644 --- a/core/racing/domain/value-objects/TeamDescription.ts +++ b/core/racing/domain/value-objects/TeamDescription.ts @@ -1,8 +1,13 @@ import { RacingDomainValidationError } from '../errors/RacingDomainError'; +import type { IValueObject } from '@core/shared/domain'; -export class TeamDescription { +export class TeamDescription implements IValueObject { private constructor(private readonly value: string) {} + get props(): string { + return this.value; + } + static create(value: string): TeamDescription { if (!value || value.trim().length === 0) { throw new RacingDomainValidationError('Team description is required'); @@ -14,7 +19,7 @@ export class TeamDescription { return this.value; } - equals(other: TeamDescription): boolean { - return this.value === other.value; + equals(other: IValueObject): boolean { + return this.value === other.props; } } \ No newline at end of file diff --git a/core/racing/domain/value-objects/TeamName.test.ts b/core/racing/domain/value-objects/TeamName.test.ts new file mode 100644 index 000000000..6c96473f6 --- /dev/null +++ b/core/racing/domain/value-objects/TeamName.test.ts @@ -0,0 +1,32 @@ +import { describe, it, expect } from 'vitest'; +import { TeamName } from './TeamName'; + +describe('TeamName', () => { + it('should create team name', () => { + const name = TeamName.create('Test Team'); + expect(name.toString()).toBe('Test Team'); + expect(name.props).toBe('Test Team'); + }); + + it('should trim whitespace', () => { + const name = TeamName.create(' Test Team '); + expect(name.toString()).toBe('Test Team'); + }); + + it('should throw for empty name', () => { + expect(() => TeamName.create('')).toThrow('Team name is required'); + expect(() => TeamName.create(' ')).toThrow('Team name is required'); + }); + + it('should equal same names', () => { + const n1 = TeamName.create('Team A'); + const n2 = TeamName.create('Team A'); + expect(n1.equals(n2)).toBe(true); + }); + + it('should not equal different names', () => { + const n1 = TeamName.create('Team A'); + const n2 = TeamName.create('Team B'); + expect(n1.equals(n2)).toBe(false); + }); +}); \ No newline at end of file diff --git a/core/racing/domain/value-objects/TeamName.ts b/core/racing/domain/value-objects/TeamName.ts index 805f6a5d6..be43a80a4 100644 --- a/core/racing/domain/value-objects/TeamName.ts +++ b/core/racing/domain/value-objects/TeamName.ts @@ -1,8 +1,13 @@ import { RacingDomainValidationError } from '../errors/RacingDomainError'; +import type { IValueObject } from '@core/shared/domain'; -export class TeamName { +export class TeamName implements IValueObject { private constructor(private readonly value: string) {} + get props(): string { + return this.value; + } + static create(value: string): TeamName { if (!value || value.trim().length === 0) { throw new RacingDomainValidationError('Team name is required'); @@ -14,7 +19,7 @@ export class TeamName { return this.value; } - equals(other: TeamName): boolean { - return this.value === other.value; + equals(other: IValueObject): boolean { + return this.value === other.props; } } \ No newline at end of file diff --git a/core/racing/domain/value-objects/TeamTag.test.ts b/core/racing/domain/value-objects/TeamTag.test.ts new file mode 100644 index 000000000..a8d5ccca8 --- /dev/null +++ b/core/racing/domain/value-objects/TeamTag.test.ts @@ -0,0 +1,32 @@ +import { describe, it, expect } from 'vitest'; +import { TeamTag } from './TeamTag'; + +describe('TeamTag', () => { + it('should create team tag', () => { + const tag = TeamTag.create('TAG123'); + expect(tag.toString()).toBe('TAG123'); + expect(tag.props).toBe('TAG123'); + }); + + it('should trim whitespace', () => { + const tag = TeamTag.create(' TAG '); + expect(tag.toString()).toBe('TAG'); + }); + + it('should throw for empty tag', () => { + expect(() => TeamTag.create('')).toThrow('Team tag is required'); + expect(() => TeamTag.create(' ')).toThrow('Team tag is required'); + }); + + it('should equal same tags', () => { + const t1 = TeamTag.create('TAG1'); + const t2 = TeamTag.create('TAG1'); + expect(t1.equals(t2)).toBe(true); + }); + + it('should not equal different tags', () => { + const t1 = TeamTag.create('TAG1'); + const t2 = TeamTag.create('TAG2'); + expect(t1.equals(t2)).toBe(false); + }); +}); \ No newline at end of file diff --git a/core/racing/domain/value-objects/TeamTag.ts b/core/racing/domain/value-objects/TeamTag.ts index 096df7f9e..4eda9e270 100644 --- a/core/racing/domain/value-objects/TeamTag.ts +++ b/core/racing/domain/value-objects/TeamTag.ts @@ -1,8 +1,13 @@ import { RacingDomainValidationError } from '../errors/RacingDomainError'; +import type { IValueObject } from '@core/shared/domain'; -export class TeamTag { +export class TeamTag implements IValueObject { private constructor(private readonly value: string) {} + get props(): string { + return this.value; + } + static create(value: string): TeamTag { if (!value || value.trim().length === 0) { throw new RacingDomainValidationError('Team tag is required'); @@ -14,7 +19,7 @@ export class TeamTag { return this.value; } - equals(other: TeamTag): boolean { - return this.value === other.value; + equals(other: IValueObject): boolean { + return this.value === other.props; } } \ No newline at end of file diff --git a/core/racing/domain/value-objects/TrackCountry.test.ts b/core/racing/domain/value-objects/TrackCountry.test.ts new file mode 100644 index 000000000..dc834a5ee --- /dev/null +++ b/core/racing/domain/value-objects/TrackCountry.test.ts @@ -0,0 +1,32 @@ +import { describe, it, expect } from 'vitest'; +import { TrackCountry } from './TrackCountry'; + +describe('TrackCountry', () => { + it('should create track country', () => { + const country = TrackCountry.create('USA'); + expect(country.toString()).toBe('USA'); + expect(country.props).toBe('USA'); + }); + + it('should trim whitespace', () => { + const country = TrackCountry.create(' USA '); + expect(country.toString()).toBe('USA'); + }); + + it('should throw for empty country', () => { + expect(() => TrackCountry.create('')).toThrow('Track country is required'); + expect(() => TrackCountry.create(' ')).toThrow('Track country is required'); + }); + + it('should equal same countries', () => { + const c1 = TrackCountry.create('USA'); + const c2 = TrackCountry.create('USA'); + expect(c1.equals(c2)).toBe(true); + }); + + it('should not equal different countries', () => { + const c1 = TrackCountry.create('USA'); + const c2 = TrackCountry.create('UK'); + expect(c1.equals(c2)).toBe(false); + }); +}); \ No newline at end of file diff --git a/core/racing/domain/value-objects/TrackCountry.ts b/core/racing/domain/value-objects/TrackCountry.ts index bb5d590a1..d7ea4398b 100644 --- a/core/racing/domain/value-objects/TrackCountry.ts +++ b/core/racing/domain/value-objects/TrackCountry.ts @@ -1,8 +1,13 @@ import { RacingDomainValidationError } from '../errors/RacingDomainError'; +import type { IValueObject } from '@core/shared/domain'; -export class TrackCountry { +export class TrackCountry implements IValueObject { private constructor(private readonly value: string) {} + get props(): string { + return this.value; + } + static create(value: string): TrackCountry { if (!value || value.trim().length === 0) { throw new RacingDomainValidationError('Track country is required'); @@ -14,7 +19,7 @@ export class TrackCountry { return this.value; } - equals(other: TrackCountry): boolean { - return this.value === other.value; + equals(other: IValueObject): boolean { + return this.value === other.props; } } \ No newline at end of file diff --git a/core/racing/domain/value-objects/TrackGameId.test.ts b/core/racing/domain/value-objects/TrackGameId.test.ts new file mode 100644 index 000000000..0aa2a36c4 --- /dev/null +++ b/core/racing/domain/value-objects/TrackGameId.test.ts @@ -0,0 +1,32 @@ +import { describe, it, expect } from 'vitest'; +import { TrackGameId } from './TrackGameId'; + +describe('TrackGameId', () => { + it('should create track game id', () => { + const id = TrackGameId.create('123'); + expect(id.toString()).toBe('123'); + expect(id.props).toBe('123'); + }); + + it('should trim whitespace', () => { + const id = TrackGameId.create(' 123 '); + expect(id.toString()).toBe('123'); + }); + + it('should throw for empty id', () => { + expect(() => TrackGameId.create('')).toThrow('Track game ID cannot be empty'); + expect(() => TrackGameId.create(' ')).toThrow('Track game ID cannot be empty'); + }); + + it('should equal same ids', () => { + const i1 = TrackGameId.create('123'); + const i2 = TrackGameId.create('123'); + expect(i1.equals(i2)).toBe(true); + }); + + it('should not equal different ids', () => { + const i1 = TrackGameId.create('123'); + const i2 = TrackGameId.create('456'); + expect(i1.equals(i2)).toBe(false); + }); +}); \ No newline at end of file diff --git a/core/racing/domain/value-objects/TrackGameId.ts b/core/racing/domain/value-objects/TrackGameId.ts index fdf922736..a3f9296cd 100644 --- a/core/racing/domain/value-objects/TrackGameId.ts +++ b/core/racing/domain/value-objects/TrackGameId.ts @@ -1,8 +1,13 @@ import { RacingDomainValidationError } from '../errors/RacingDomainError'; +import type { IValueObject } from '@core/shared/domain'; -export class TrackGameId { +export class TrackGameId implements IValueObject { private constructor(private readonly value: string) {} + get props(): string { + return this.value; + } + static create(value: string): TrackGameId { if (!value || value.trim().length === 0) { throw new RacingDomainValidationError('Track game ID cannot be empty'); @@ -14,7 +19,7 @@ export class TrackGameId { return this.value; } - equals(other: TrackGameId): boolean { - return this.value === other.value; + equals(other: IValueObject): boolean { + return this.value === other.props; } } \ No newline at end of file diff --git a/core/racing/domain/value-objects/TrackId.test.ts b/core/racing/domain/value-objects/TrackId.test.ts new file mode 100644 index 000000000..94b6b4b12 --- /dev/null +++ b/core/racing/domain/value-objects/TrackId.test.ts @@ -0,0 +1,32 @@ +import { describe, it, expect } from 'vitest'; +import { TrackId } from './TrackId'; + +describe('TrackId', () => { + it('should create track id', () => { + const id = TrackId.create('track-123'); + expect(id.toString()).toBe('track-123'); + expect(id.props).toBe('track-123'); + }); + + it('should trim whitespace', () => { + const id = TrackId.create(' track-123 '); + expect(id.toString()).toBe('track-123'); + }); + + it('should throw for empty id', () => { + expect(() => TrackId.create('')).toThrow('Track ID cannot be empty'); + expect(() => TrackId.create(' ')).toThrow('Track ID cannot be empty'); + }); + + it('should equal same ids', () => { + const i1 = TrackId.create('track-123'); + const i2 = TrackId.create('track-123'); + expect(i1.equals(i2)).toBe(true); + }); + + it('should not equal different ids', () => { + const i1 = TrackId.create('track-123'); + const i2 = TrackId.create('track-456'); + expect(i1.equals(i2)).toBe(false); + }); +}); \ No newline at end of file diff --git a/core/racing/domain/value-objects/TrackId.ts b/core/racing/domain/value-objects/TrackId.ts index 08824ff44..da8fc449d 100644 --- a/core/racing/domain/value-objects/TrackId.ts +++ b/core/racing/domain/value-objects/TrackId.ts @@ -1,8 +1,13 @@ import { RacingDomainValidationError } from '../errors/RacingDomainError'; +import type { IValueObject } from '@core/shared/domain'; -export class TrackId { +export class TrackId implements IValueObject { private constructor(private readonly value: string) {} + get props(): string { + return this.value; + } + static create(value: string): TrackId { if (!value || value.trim().length === 0) { throw new RacingDomainValidationError('Track ID cannot be empty'); @@ -14,7 +19,7 @@ export class TrackId { return this.value; } - equals(other: TrackId): boolean { - return this.value === other.value; + equals(other: IValueObject): boolean { + return this.value === other.props; } } \ No newline at end of file diff --git a/core/racing/domain/value-objects/TrackImageUrl.test.ts b/core/racing/domain/value-objects/TrackImageUrl.test.ts new file mode 100644 index 000000000..52323dde7 --- /dev/null +++ b/core/racing/domain/value-objects/TrackImageUrl.test.ts @@ -0,0 +1,45 @@ +import { describe, it, expect } from 'vitest'; +import { TrackImageUrl } from './TrackImageUrl'; + +describe('TrackImageUrl', () => { + it('should create track image url', () => { + const url = TrackImageUrl.create('http://example.com/image.jpg'); + expect(url.toString()).toBe('http://example.com/image.jpg'); + expect(url.props).toBe('http://example.com/image.jpg'); + }); + + it('should allow undefined', () => { + const url = TrackImageUrl.create(undefined); + expect(url.toString()).toBeUndefined(); + expect(url.props).toBeUndefined(); + }); + + it('should throw for empty string', () => { + expect(() => TrackImageUrl.create('')).toThrow('Track image URL cannot be empty string'); + expect(() => TrackImageUrl.create(' ')).toThrow('Track image URL cannot be empty string'); + }); + + it('should equal same urls', () => { + const u1 = TrackImageUrl.create('http://example.com/image.jpg'); + const u2 = TrackImageUrl.create('http://example.com/image.jpg'); + expect(u1.equals(u2)).toBe(true); + }); + + it('should equal undefined', () => { + const u1 = TrackImageUrl.create(undefined); + const u2 = TrackImageUrl.create(undefined); + expect(u1.equals(u2)).toBe(true); + }); + + it('should not equal different urls', () => { + const u1 = TrackImageUrl.create('http://example.com/image1.jpg'); + const u2 = TrackImageUrl.create('http://example.com/image2.jpg'); + expect(u1.equals(u2)).toBe(false); + }); + + it('should not equal url and undefined', () => { + const u1 = TrackImageUrl.create('http://example.com/image.jpg'); + const u2 = TrackImageUrl.create(undefined); + expect(u1.equals(u2)).toBe(false); + }); +}); \ No newline at end of file diff --git a/core/racing/domain/value-objects/TrackImageUrl.ts b/core/racing/domain/value-objects/TrackImageUrl.ts index 8ca9fd657..16283169c 100644 --- a/core/racing/domain/value-objects/TrackImageUrl.ts +++ b/core/racing/domain/value-objects/TrackImageUrl.ts @@ -1,8 +1,13 @@ import { RacingDomainValidationError } from '../errors/RacingDomainError'; +import type { IValueObject } from '@core/shared/domain'; -export class TrackImageUrl { +export class TrackImageUrl implements IValueObject { private constructor(private readonly value: string | undefined) {} + get props(): string | undefined { + return this.value; + } + static create(value: string | undefined): TrackImageUrl { // Allow undefined or valid URL, but for simplicity, just check if string is not empty if provided if (value !== undefined && value.trim().length === 0) { @@ -15,7 +20,7 @@ export class TrackImageUrl { return this.value; } - equals(other: TrackImageUrl): boolean { - return this.value === other.value; + equals(other: IValueObject): boolean { + return this.value === other.props; } } \ No newline at end of file diff --git a/core/racing/domain/value-objects/TrackLength.test.ts b/core/racing/domain/value-objects/TrackLength.test.ts new file mode 100644 index 000000000..4f35534f3 --- /dev/null +++ b/core/racing/domain/value-objects/TrackLength.test.ts @@ -0,0 +1,27 @@ +import { describe, it, expect } from 'vitest'; +import { TrackLength } from './TrackLength'; + +describe('TrackLength', () => { + it('should create track length', () => { + const length = TrackLength.create(1000); + expect(length.toNumber()).toBe(1000); + expect(length.props).toBe(1000); + }); + + it('should throw for non-positive length', () => { + expect(() => TrackLength.create(0)).toThrow('Track length must be positive'); + expect(() => TrackLength.create(-1)).toThrow('Track length must be positive'); + }); + + it('should equal same lengths', () => { + const l1 = TrackLength.create(1000); + const l2 = TrackLength.create(1000); + expect(l1.equals(l2)).toBe(true); + }); + + it('should not equal different lengths', () => { + const l1 = TrackLength.create(1000); + const l2 = TrackLength.create(2000); + expect(l1.equals(l2)).toBe(false); + }); +}); \ No newline at end of file diff --git a/core/racing/domain/value-objects/TrackLength.ts b/core/racing/domain/value-objects/TrackLength.ts index 618df4d79..37c508aad 100644 --- a/core/racing/domain/value-objects/TrackLength.ts +++ b/core/racing/domain/value-objects/TrackLength.ts @@ -1,8 +1,13 @@ import { RacingDomainValidationError } from '../errors/RacingDomainError'; +import type { IValueObject } from '@core/shared/domain'; -export class TrackLength { +export class TrackLength implements IValueObject { private constructor(private readonly value: number) {} + get props(): number { + return this.value; + } + static create(value: number): TrackLength { if (value <= 0) { throw new RacingDomainValidationError('Track length must be positive'); @@ -14,7 +19,7 @@ export class TrackLength { return this.value; } - equals(other: TrackLength): boolean { - return this.value === other.value; + equals(other: IValueObject): boolean { + return this.value === other.props; } } \ No newline at end of file diff --git a/core/racing/domain/value-objects/TrackName.test.ts b/core/racing/domain/value-objects/TrackName.test.ts new file mode 100644 index 000000000..16b2638c4 --- /dev/null +++ b/core/racing/domain/value-objects/TrackName.test.ts @@ -0,0 +1,32 @@ +import { describe, it, expect } from 'vitest'; +import { TrackName } from './TrackName'; + +describe('TrackName', () => { + it('should create track name', () => { + const name = TrackName.create('Silverstone'); + expect(name.toString()).toBe('Silverstone'); + expect(name.props).toBe('Silverstone'); + }); + + it('should trim whitespace', () => { + const name = TrackName.create(' Silverstone '); + expect(name.toString()).toBe('Silverstone'); + }); + + it('should throw for empty name', () => { + expect(() => TrackName.create('')).toThrow('Track name is required'); + expect(() => TrackName.create(' ')).toThrow('Track name is required'); + }); + + it('should equal same names', () => { + const n1 = TrackName.create('Silverstone'); + const n2 = TrackName.create('Silverstone'); + expect(n1.equals(n2)).toBe(true); + }); + + it('should not equal different names', () => { + const n1 = TrackName.create('Silverstone'); + const n2 = TrackName.create('Monza'); + expect(n1.equals(n2)).toBe(false); + }); +}); \ No newline at end of file diff --git a/core/racing/domain/value-objects/TrackName.ts b/core/racing/domain/value-objects/TrackName.ts index 2cdcc2bda..b6df04fc1 100644 --- a/core/racing/domain/value-objects/TrackName.ts +++ b/core/racing/domain/value-objects/TrackName.ts @@ -1,8 +1,13 @@ import { RacingDomainValidationError } from '../errors/RacingDomainError'; +import type { IValueObject } from '@core/shared/domain'; -export class TrackName { +export class TrackName implements IValueObject { private constructor(private readonly value: string) {} + get props(): string { + return this.value; + } + static create(value: string): TrackName { if (!value || value.trim().length === 0) { throw new RacingDomainValidationError('Track name is required'); @@ -14,7 +19,7 @@ export class TrackName { return this.value; } - equals(other: TrackName): boolean { - return this.value === other.value; + equals(other: IValueObject): boolean { + return this.value === other.props; } } \ No newline at end of file diff --git a/core/racing/domain/value-objects/TrackShortName.test.ts b/core/racing/domain/value-objects/TrackShortName.test.ts new file mode 100644 index 000000000..81b949db2 --- /dev/null +++ b/core/racing/domain/value-objects/TrackShortName.test.ts @@ -0,0 +1,32 @@ +import { describe, it, expect } from 'vitest'; +import { TrackShortName } from './TrackShortName'; + +describe('TrackShortName', () => { + it('should create track short name', () => { + const name = TrackShortName.create('SIL'); + expect(name.toString()).toBe('SIL'); + expect(name.props).toBe('SIL'); + }); + + it('should trim whitespace', () => { + const name = TrackShortName.create(' SIL '); + expect(name.toString()).toBe('SIL'); + }); + + it('should throw for empty name', () => { + expect(() => TrackShortName.create('')).toThrow('Track short name is required'); + expect(() => TrackShortName.create(' ')).toThrow('Track short name is required'); + }); + + it('should equal same names', () => { + const n1 = TrackShortName.create('SIL'); + const n2 = TrackShortName.create('SIL'); + expect(n1.equals(n2)).toBe(true); + }); + + it('should not equal different names', () => { + const n1 = TrackShortName.create('SIL'); + const n2 = TrackShortName.create('MON'); + expect(n1.equals(n2)).toBe(false); + }); +}); \ No newline at end of file diff --git a/core/racing/domain/value-objects/TrackShortName.ts b/core/racing/domain/value-objects/TrackShortName.ts index f0ac8cc00..33a1db930 100644 --- a/core/racing/domain/value-objects/TrackShortName.ts +++ b/core/racing/domain/value-objects/TrackShortName.ts @@ -1,8 +1,13 @@ import { RacingDomainValidationError } from '../errors/RacingDomainError'; +import type { IValueObject } from '@core/shared/domain'; -export class TrackShortName { +export class TrackShortName implements IValueObject { private constructor(private readonly value: string) {} + get props(): string { + return this.value; + } + static create(value: string): TrackShortName { if (!value || value.trim().length === 0) { throw new RacingDomainValidationError('Track short name is required'); @@ -14,7 +19,7 @@ export class TrackShortName { return this.value; } - equals(other: TrackShortName): boolean { - return this.value === other.value; + equals(other: IValueObject): boolean { + return this.value === other.props; } } \ No newline at end of file diff --git a/core/racing/domain/value-objects/TrackTurns.test.ts b/core/racing/domain/value-objects/TrackTurns.test.ts new file mode 100644 index 000000000..9197f7955 --- /dev/null +++ b/core/racing/domain/value-objects/TrackTurns.test.ts @@ -0,0 +1,31 @@ +import { describe, it, expect } from 'vitest'; +import { TrackTurns } from './TrackTurns'; + +describe('TrackTurns', () => { + it('should create track turns', () => { + const turns = TrackTurns.create(10); + expect(turns.toNumber()).toBe(10); + expect(turns.props).toBe(10); + }); + + it('should allow zero turns', () => { + const turns = TrackTurns.create(0); + expect(turns.toNumber()).toBe(0); + }); + + it('should throw for negative turns', () => { + expect(() => TrackTurns.create(-1)).toThrow('Track turns cannot be negative'); + }); + + it('should equal same turns', () => { + const t1 = TrackTurns.create(10); + const t2 = TrackTurns.create(10); + expect(t1.equals(t2)).toBe(true); + }); + + it('should not equal different turns', () => { + const t1 = TrackTurns.create(10); + const t2 = TrackTurns.create(20); + expect(t1.equals(t2)).toBe(false); + }); +}); \ No newline at end of file diff --git a/core/racing/domain/value-objects/TrackTurns.ts b/core/racing/domain/value-objects/TrackTurns.ts index 427b549ef..ba9f1f928 100644 --- a/core/racing/domain/value-objects/TrackTurns.ts +++ b/core/racing/domain/value-objects/TrackTurns.ts @@ -1,8 +1,13 @@ import { RacingDomainValidationError } from '../errors/RacingDomainError'; +import type { IValueObject } from '@core/shared/domain'; -export class TrackTurns { +export class TrackTurns implements IValueObject { private constructor(private readonly value: number) {} + get props(): number { + return this.value; + } + static create(value: number): TrackTurns { if (value < 0) { throw new RacingDomainValidationError('Track turns cannot be negative'); @@ -14,7 +19,7 @@ export class TrackTurns { return this.value; } - equals(other: TrackTurns): boolean { - return this.value === other.value; + equals(other: IValueObject): boolean { + return this.value === other.props; } } \ No newline at end of file diff --git a/core/racing/domain/value-objects/DriverBio.test.ts b/core/racing/domain/value-objects/driver/DriverBio.test.ts similarity index 100% rename from core/racing/domain/value-objects/DriverBio.test.ts rename to core/racing/domain/value-objects/driver/DriverBio.test.ts diff --git a/core/racing/domain/value-objects/driver/DriverBio.ts b/core/racing/domain/value-objects/driver/DriverBio.ts new file mode 100644 index 000000000..1b231ea99 --- /dev/null +++ b/core/racing/domain/value-objects/driver/DriverBio.ts @@ -0,0 +1,29 @@ +import { RacingDomainValidationError } from '../../errors/RacingDomainError'; +import type { IValueObject } from '@core/shared/domain'; + +export interface DriverBioProps { + value: string; +} + +export class DriverBio implements IValueObject { + private constructor(private readonly value: string) {} + + static create(value: string): DriverBio { + if (value.length > 500) { + throw new RacingDomainValidationError('Driver bio cannot exceed 500 characters'); + } + return new DriverBio(value); + } + + toString(): string { + return this.value; + } + + equals(other: IValueObject): boolean { + return this.props.value === other.props.value; + } + + get props(): DriverBioProps { + return { value: this.value }; + } +} \ No newline at end of file diff --git a/core/racing/domain/value-objects/DriverId.test.ts b/core/racing/domain/value-objects/driver/DriverId.test.ts similarity index 100% rename from core/racing/domain/value-objects/DriverId.test.ts rename to core/racing/domain/value-objects/driver/DriverId.test.ts diff --git a/core/racing/domain/value-objects/driver/DriverId.ts b/core/racing/domain/value-objects/driver/DriverId.ts new file mode 100644 index 000000000..558c42f31 --- /dev/null +++ b/core/racing/domain/value-objects/driver/DriverId.ts @@ -0,0 +1,29 @@ +import { RacingDomainValidationError } from '../../errors/RacingDomainError'; +import type { IValueObject } from '@core/shared/domain'; + +export interface DriverIdProps { + value: string; +} + +export class DriverId implements IValueObject { + private constructor(private readonly value: string) {} + + static create(value: string): DriverId { + if (!value || value.trim().length === 0) { + throw new RacingDomainValidationError('Driver ID is required'); + } + return new DriverId(value.trim()); + } + + toString(): string { + return this.value; + } + + equals(other: IValueObject): boolean { + return this.props.value === other.props.value; + } + + get props(): DriverIdProps { + return { value: this.value }; + } +} \ No newline at end of file diff --git a/core/racing/domain/value-objects/DriverName.test.ts b/core/racing/domain/value-objects/driver/DriverName.test.ts similarity index 100% rename from core/racing/domain/value-objects/DriverName.test.ts rename to core/racing/domain/value-objects/driver/DriverName.test.ts diff --git a/core/racing/domain/value-objects/driver/DriverName.ts b/core/racing/domain/value-objects/driver/DriverName.ts new file mode 100644 index 000000000..584e4bfc4 --- /dev/null +++ b/core/racing/domain/value-objects/driver/DriverName.ts @@ -0,0 +1,29 @@ +import { RacingDomainValidationError } from '../../errors/RacingDomainError'; +import type { IValueObject } from '@core/shared/domain'; + +export interface DriverNameProps { + value: string; +} + +export class DriverName implements IValueObject { + private constructor(private readonly value: string) {} + + static create(value: string): DriverName { + if (!value || value.trim().length === 0) { + throw new RacingDomainValidationError('Driver name is required'); + } + return new DriverName(value.trim()); + } + + toString(): string { + return this.value; + } + + equals(other: IValueObject): boolean { + return this.props.value === other.props.value; + } + + get props(): DriverNameProps { + return { value: this.value }; + } +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index aca8f0f44..9fcb32ea2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,7 @@ "testing/*" ], "dependencies": { - "@gridpilot/social": "file:core/social", + "@core/social": "file:core/social", "@nestjs/swagger": "11.2.3", "bcrypt": "^6.0.0", "electron-vite": "3.1.0", @@ -56,6 +56,7 @@ } }, "apps/api": { + "name": "@gridpilot/api", "version": "1.0.0", "license": "ISC", "dependencies": { @@ -71,12 +72,10 @@ "pg": "^8.12.0", "reflect-metadata": "^0.1.13", "rxjs": "^7.8.1", - "ts-jest": "^29.4.6", "typeorm": "^0.3.20" }, "devDependencies": { "@nestjs/testing": "^10.4.20", - "@types/jest": "^30.0.0", "ts-node-dev": "^2.0.0" } }, @@ -233,12 +232,12 @@ "name": "@gridpilot/website", "version": "0.1.0", "dependencies": { + "@core/identity": "file:../../core/identity", + "@core/notifications": "file:../../core/notifications", + "@core/racing": "file:../../core/racing", + "@core/social": "file:../../core/social", + "@core/testing-support": "file:../../core/testing-support", "@faker-js/faker": "^9.2.0", - "@gridpilot/identity": "file:../../core/identity", - "@gridpilot/notifications": "file:../../core/notifications", - "@gridpilot/racing": "file:../../core/racing", - "@gridpilot/social": "file:../../core/social", - "@gridpilot/testing-support": "file:../../core/testing-support", "@vercel/kv": "^3.0.0", "electron": "39.2.7", "framer-motion": "^12.23.25", @@ -378,11 +377,13 @@ }, "core/analytics": { "name": "@gridpilot/analytics", - "version": "0.1.0" + "version": "0.1.0", + "extraneous": true }, "core/automation": { "name": "@gridpilot/automation", - "version": "0.1.0" + "version": "0.1.0", + "extraneous": true }, "core/identity": { "name": "@gridpilot/identity", @@ -393,7 +394,8 @@ }, "core/media": { "name": "@gridpilot/media", - "version": "0.1.0" + "version": "0.1.0", + "extraneous": true }, "core/notifications": { "name": "@gridpilot/notifications", @@ -443,7 +445,8 @@ }, "core/shared": { "name": "@gridpilot/shared", - "version": "0.1.0" + "version": "0.1.0", + "extraneous": true }, "core/social": { "name": "@gridpilot/social", @@ -679,245 +682,6 @@ "node": ">=6.0.0" } }, - "node_modules/@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-bigint": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", - "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-class-properties": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", - "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-class-static-block": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", - "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz", - "integrity": "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==", - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-import-meta": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", - "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz", - "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==", - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", - "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-private-property-in-object": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", - "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-top-level-await": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", - "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz", - "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "node_modules/@babel/plugin-transform-arrow-functions": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.27.1.tgz", @@ -1020,13 +784,6 @@ "node": ">=6.9.0" } }, - "node_modules/@bcoe/v8-coverage": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", - "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", - "license": "MIT", - "peer": true - }, "node_modules/@borewit/text-codec": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/@borewit/text-codec/-/text-codec-0.1.1.tgz", @@ -1065,6 +822,26 @@ "node": ">=0.1.90" } }, + "node_modules/@core/identity": { + "resolved": "core/identity", + "link": true + }, + "node_modules/@core/notifications": { + "resolved": "core/notifications", + "link": true + }, + "node_modules/@core/racing": { + "resolved": "core/racing", + "link": true + }, + "node_modules/@core/social": { + "resolved": "core/social", + "link": true + }, + "node_modules/@core/testing-support": { + "resolved": "core/testing-support", + "link": true + }, "node_modules/@cspotcode/source-map-support": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", @@ -1421,6 +1198,7 @@ "version": "1.7.1", "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.7.1.tgz", "integrity": "sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg==", + "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -1442,6 +1220,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz", "integrity": "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==", + "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -1967,46 +1746,14 @@ "npm": ">=9.0.0" } }, - "node_modules/@gridpilot/analytics": { - "resolved": "core/analytics", - "link": true - }, - "node_modules/@gridpilot/automation": { - "resolved": "core/automation", + "node_modules/@gridpilot/api": { + "resolved": "apps/api", "link": true }, "node_modules/@gridpilot/companion": { "resolved": "apps/companion", "link": true }, - "node_modules/@gridpilot/identity": { - "resolved": "core/identity", - "link": true - }, - "node_modules/@gridpilot/media": { - "resolved": "core/media", - "link": true - }, - "node_modules/@gridpilot/notifications": { - "resolved": "core/notifications", - "link": true - }, - "node_modules/@gridpilot/racing": { - "resolved": "core/racing", - "link": true - }, - "node_modules/@gridpilot/shared": { - "resolved": "core/shared", - "link": true - }, - "node_modules/@gridpilot/social": { - "resolved": "core/social", - "link": true - }, - "node_modules/@gridpilot/testing-support": { - "resolved": "core/testing-support", - "link": true - }, "node_modules/@gridpilot/website": { "resolved": "apps/website", "link": true @@ -2605,501 +2352,6 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/@istanbuljs/load-nyc-config": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", - "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", - "license": "ISC", - "peer": true, - "dependencies": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "get-package-type": "^0.1.0", - "js-yaml": "^3.13.1", - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "license": "MIT", - "peer": true, - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "license": "MIT", - "peer": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { - "version": "3.14.2", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", - "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", - "license": "MIT", - "peer": true, - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "license": "MIT", - "peer": true, - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "license": "MIT", - "peer": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "license": "MIT", - "peer": true, - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "license": "BSD-3-Clause", - "peer": true - }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/console": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-30.2.0.tgz", - "integrity": "sha512-+O1ifRjkvYIkBqASKWgLxrpEhQAAE7hY77ALLUufSk5717KfOShg6IbqLmdsLMPdUiFvA2kTs0R7YZy+l0IzZQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "@jest/types": "30.2.0", - "@types/node": "*", - "chalk": "^4.1.2", - "jest-message-util": "30.2.0", - "jest-util": "30.2.0", - "slash": "^3.0.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/core": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-30.2.0.tgz", - "integrity": "sha512-03W6IhuhjqTlpzh/ojut/pDB2LPRygyWX8ExpgHtQA8H/3K7+1vKmcINx5UzeOX1se6YEsBsOHQ1CRzf3fOwTQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "@jest/console": "30.2.0", - "@jest/pattern": "30.0.1", - "@jest/reporters": "30.2.0", - "@jest/test-result": "30.2.0", - "@jest/transform": "30.2.0", - "@jest/types": "30.2.0", - "@types/node": "*", - "ansi-escapes": "^4.3.2", - "chalk": "^4.1.2", - "ci-info": "^4.2.0", - "exit-x": "^0.2.2", - "graceful-fs": "^4.2.11", - "jest-changed-files": "30.2.0", - "jest-config": "30.2.0", - "jest-haste-map": "30.2.0", - "jest-message-util": "30.2.0", - "jest-regex-util": "30.0.1", - "jest-resolve": "30.2.0", - "jest-resolve-dependencies": "30.2.0", - "jest-runner": "30.2.0", - "jest-runtime": "30.2.0", - "jest-snapshot": "30.2.0", - "jest-util": "30.2.0", - "jest-validate": "30.2.0", - "jest-watcher": "30.2.0", - "micromatch": "^4.0.8", - "pretty-format": "30.2.0", - "slash": "^3.0.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/@jest/core/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/core/node_modules/pretty-format": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.2.0.tgz", - "integrity": "sha512-9uBdv/B4EefsuAL+pWqueZyZS2Ba+LxfFeQ9DN14HU4bN8bhaxKdkpjpB6fs9+pSjIBu+FXQHImEg8j/Lw0+vA==", - "license": "MIT", - "peer": true, - "dependencies": { - "@jest/schemas": "30.0.5", - "ansi-styles": "^5.2.0", - "react-is": "^18.3.1" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/core/node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "license": "MIT", - "peer": true - }, - "node_modules/@jest/diff-sequences": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.0.1.tgz", - "integrity": "sha512-n5H8QLDJ47QqbCNn5SuFjCRDrOLEZ0h8vAHCK5RL9Ls7Xa8AQLa/YxAc9UjFqoEDM48muwtBGjtMY5cr0PLDCw==", - "license": "MIT", - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/environment": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.2.0.tgz", - "integrity": "sha512-/QPTL7OBJQ5ac09UDRa3EQes4gt1FTEG/8jZ/4v5IVzx+Cv7dLxlVIvfvSVRiiX2drWyXeBjkMSR8hvOWSog5g==", - "license": "MIT", - "peer": true, - "dependencies": { - "@jest/fake-timers": "30.2.0", - "@jest/types": "30.2.0", - "@types/node": "*", - "jest-mock": "30.2.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/expect": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-30.2.0.tgz", - "integrity": "sha512-V9yxQK5erfzx99Sf+7LbhBwNWEZ9eZay8qQ9+JSC0TrMR1pMDHLMY+BnVPacWU6Jamrh252/IKo4F1Xn/zfiqA==", - "license": "MIT", - "peer": true, - "dependencies": { - "expect": "30.2.0", - "jest-snapshot": "30.2.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/expect-utils": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.2.0.tgz", - "integrity": "sha512-1JnRfhqpD8HGpOmQp180Fo9Zt69zNtC+9lR+kT7NVL05tNXIi+QC8Csz7lfidMoVLPD3FnOtcmp0CEFnxExGEA==", - "license": "MIT", - "dependencies": { - "@jest/get-type": "30.1.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/fake-timers": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.2.0.tgz", - "integrity": "sha512-HI3tRLjRxAbBy0VO8dqqm7Hb2mIa8d5bg/NJkyQcOk7V118ObQML8RC5luTF/Zsg4474a+gDvhce7eTnP4GhYw==", - "license": "MIT", - "peer": true, - "dependencies": { - "@jest/types": "30.2.0", - "@sinonjs/fake-timers": "^13.0.0", - "@types/node": "*", - "jest-message-util": "30.2.0", - "jest-mock": "30.2.0", - "jest-util": "30.2.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/get-type": { - "version": "30.1.0", - "resolved": "https://registry.npmjs.org/@jest/get-type/-/get-type-30.1.0.tgz", - "integrity": "sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA==", - "license": "MIT", - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/globals": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-30.2.0.tgz", - "integrity": "sha512-b63wmnKPaK+6ZZfpYhz9K61oybvbI1aMcIs80++JI1O1rR1vaxHUCNqo3ITu6NU0d4V34yZFoHMn/uoKr/Rwfw==", - "license": "MIT", - "peer": true, - "dependencies": { - "@jest/environment": "30.2.0", - "@jest/expect": "30.2.0", - "@jest/types": "30.2.0", - "jest-mock": "30.2.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/pattern": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.0.1.tgz", - "integrity": "sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==", - "license": "MIT", - "dependencies": { - "@types/node": "*", - "jest-regex-util": "30.0.1" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/reporters": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-30.2.0.tgz", - "integrity": "sha512-DRyW6baWPqKMa9CzeiBjHwjd8XeAyco2Vt8XbcLFjiwCOEKOvy82GJ8QQnJE9ofsxCMPjH4MfH8fCWIHHDKpAQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "30.2.0", - "@jest/test-result": "30.2.0", - "@jest/transform": "30.2.0", - "@jest/types": "30.2.0", - "@jridgewell/trace-mapping": "^0.3.25", - "@types/node": "*", - "chalk": "^4.1.2", - "collect-v8-coverage": "^1.0.2", - "exit-x": "^0.2.2", - "glob": "^10.3.10", - "graceful-fs": "^4.2.11", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^6.0.0", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^5.0.0", - "istanbul-reports": "^3.1.3", - "jest-message-util": "30.2.0", - "jest-util": "30.2.0", - "jest-worker": "30.2.0", - "slash": "^3.0.0", - "string-length": "^4.0.2", - "v8-to-istanbul": "^9.0.1" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/@jest/schemas": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", - "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", - "license": "MIT", - "dependencies": { - "@sinclair/typebox": "^0.34.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/snapshot-utils": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/snapshot-utils/-/snapshot-utils-30.2.0.tgz", - "integrity": "sha512-0aVxM3RH6DaiLcjj/b0KrIBZhSX1373Xci4l3cW5xiUWPctZ59zQ7jj4rqcJQ/Z8JuN/4wX3FpJSa3RssVvCug==", - "license": "MIT", - "peer": true, - "dependencies": { - "@jest/types": "30.2.0", - "chalk": "^4.1.2", - "graceful-fs": "^4.2.11", - "natural-compare": "^1.4.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/source-map": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-30.0.1.tgz", - "integrity": "sha512-MIRWMUUR3sdbP36oyNyhbThLHyJ2eEDClPCiHVbrYAe5g3CHRArIVpBw7cdSB5fr+ofSfIb2Tnsw8iEHL0PYQg==", - "license": "MIT", - "peer": true, - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.25", - "callsites": "^3.1.0", - "graceful-fs": "^4.2.11" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/test-result": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-30.2.0.tgz", - "integrity": "sha512-RF+Z+0CCHkARz5HT9mcQCBulb1wgCP3FBvl9VFokMX27acKphwyQsNuWH3c+ojd1LeWBLoTYoxF0zm6S/66mjg==", - "license": "MIT", - "peer": true, - "dependencies": { - "@jest/console": "30.2.0", - "@jest/types": "30.2.0", - "@types/istanbul-lib-coverage": "^2.0.6", - "collect-v8-coverage": "^1.0.2" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/test-sequencer": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-30.2.0.tgz", - "integrity": "sha512-wXKgU/lk8fKXMu/l5Hog1R61bL4q5GCdT6OJvdAFz1P+QrpoFuLU68eoKuVc4RbrTtNnTL5FByhWdLgOPSph+Q==", - "license": "MIT", - "peer": true, - "dependencies": { - "@jest/test-result": "30.2.0", - "graceful-fs": "^4.2.11", - "jest-haste-map": "30.2.0", - "slash": "^3.0.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/transform": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.2.0.tgz", - "integrity": "sha512-XsauDV82o5qXbhalKxD7p4TZYYdwcaEXC77PPD2HixEFF+6YGppjrAAQurTl2ECWcEomHBMMNS9AH3kcCFx8jA==", - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/core": "^7.27.4", - "@jest/types": "30.2.0", - "@jridgewell/trace-mapping": "^0.3.25", - "babel-plugin-istanbul": "^7.0.1", - "chalk": "^4.1.2", - "convert-source-map": "^2.0.0", - "fast-json-stable-stringify": "^2.1.0", - "graceful-fs": "^4.2.11", - "jest-haste-map": "30.2.0", - "jest-regex-util": "30.0.1", - "jest-util": "30.2.0", - "micromatch": "^4.0.8", - "pirates": "^4.0.7", - "slash": "^3.0.0", - "write-file-atomic": "^5.0.1" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/types": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.2.0.tgz", - "integrity": "sha512-H9xg1/sfVvyfU7o3zMfBEjQ1gcsdeTMgqHoYdN79tuLqfTtuu7WckRA1R5whDwOzxaZAeMKTYWqP+WCAi0CHsg==", - "license": "MIT", - "dependencies": { - "@jest/pattern": "30.0.1", - "@jest/schemas": "30.0.5", - "@types/istanbul-lib-coverage": "^2.0.6", - "@types/istanbul-reports": "^3.0.4", - "@types/node": "*", - "@types/yargs": "^17.0.33", - "chalk": "^4.1.2" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", @@ -3164,6 +2416,7 @@ "version": "0.2.12", "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", "integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==", + "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -3658,19 +2911,6 @@ "node": ">=14" } }, - "node_modules/@pkgr/core": { - "version": "0.2.9", - "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz", - "integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==", - "license": "MIT", - "peer": true, - "engines": { - "node": "^12.20.0 || ^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/pkgr" - } - }, "node_modules/@playwright/test": { "version": "1.57.0", "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.57.0.tgz", @@ -4041,12 +3281,6 @@ "hasInstallScript": true, "license": "Apache-2.0" }, - "node_modules/@sinclair/typebox": { - "version": "0.34.41", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.41.tgz", - "integrity": "sha512-6gS8pZzSXdyRHTIqoqSVknxolr1kzfy4/CeDnrzsVz8TTIWUbOBr6gnzOmTYJ3eXQNh4IYHIGi5aIL7sOZ2G/g==", - "license": "MIT" - }, "node_modules/@sindresorhus/is": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", @@ -4059,26 +3293,6 @@ "url": "https://github.com/sindresorhus/is?sponsor=1" } }, - "node_modules/@sinonjs/commons": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", - "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", - "license": "BSD-3-Clause", - "peer": true, - "dependencies": { - "type-detect": "4.0.8" - } - }, - "node_modules/@sinonjs/fake-timers": { - "version": "13.0.5", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-13.0.5.tgz", - "integrity": "sha512-36/hTbH2uaWuGVERyC6da9YwGWnzUZXuPro/F2LfsdOsLnCojz/iSH8MxUt/FD2S5XBSVPhmArFUXcpCQ2Hkiw==", - "license": "BSD-3-Clause", - "peer": true, - "dependencies": { - "@sinonjs/commons": "^3.0.1" - } - }, "node_modules/@sqltools/formatter": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/@sqltools/formatter/-/formatter-1.2.5.tgz", @@ -4271,6 +3485,7 @@ "version": "0.10.1", "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -4289,6 +3504,7 @@ "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, "license": "MIT", "dependencies": { "@babel/parser": "^7.20.7", @@ -4302,6 +3518,7 @@ "version": "7.27.0", "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, "license": "MIT", "dependencies": { "@babel/types": "^7.0.0" @@ -4311,6 +3528,7 @@ "version": "7.4.4", "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, "license": "MIT", "dependencies": { "@babel/parser": "^7.1.0", @@ -4321,6 +3539,7 @@ "version": "7.28.0", "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, "license": "MIT", "dependencies": { "@babel/types": "^7.28.2" @@ -4441,76 +3660,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/istanbul-lib-coverage": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", - "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", - "license": "MIT" - }, - "node_modules/@types/istanbul-lib-report": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", - "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", - "license": "MIT", - "dependencies": { - "@types/istanbul-lib-coverage": "*" - } - }, - "node_modules/@types/istanbul-reports": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", - "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", - "license": "MIT", - "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/@types/jest": { - "version": "30.0.0", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-30.0.0.tgz", - "integrity": "sha512-XTYugzhuwqWjws0CVz8QpM36+T+Dz5mTEBKhNs/esGLnCIlGdRy+Dq78NRjd7ls7r8BC8ZRMOrKlkO1hU0JOwA==", - "dev": true, - "license": "MIT", - "dependencies": { - "expect": "^30.0.0", - "pretty-format": "^30.0.0" - } - }, - "node_modules/@types/jest/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@types/jest/node_modules/pretty-format": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.2.0.tgz", - "integrity": "sha512-9uBdv/B4EefsuAL+pWqueZyZS2Ba+LxfFeQ9DN14HU4bN8bhaxKdkpjpB6fs9+pSjIBu+FXQHImEg8j/Lw0+vA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/schemas": "30.0.5", - "ansi-styles": "^5.2.0", - "react-is": "^18.3.1" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@types/jest/node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/jsdom": { "version": "27.0.0", "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-27.0.0.tgz", @@ -4672,12 +3821,6 @@ "@types/node": "*" } }, - "node_modules/@types/stack-utils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", - "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", - "license": "MIT" - }, "node_modules/@types/strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@types/strip-bom/-/strip-bom-3.0.0.tgz", @@ -4712,21 +3855,6 @@ "integrity": "sha512-T8L6i7wCuyoK8A/ZeLYt1+q0ty3Zb9+qbSSvrIVitzT3YjZqkTZ40IbRsPanlB4h1QB3JVL1SYCdR6ngtFYcuA==", "license": "MIT" }, - "node_modules/@types/yargs": { - "version": "17.0.35", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", - "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==", - "license": "MIT", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@types/yargs-parser": { - "version": "21.0.3", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", - "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", - "license": "MIT" - }, "node_modules/@types/yauzl": { "version": "2.10.3", "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", @@ -4965,6 +4093,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "dev": true, "license": "ISC" }, "node_modules/@unrs/resolver-binding-android-arm-eabi": { @@ -4974,6 +4103,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4987,6 +4117,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -5000,6 +4131,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -5013,6 +4145,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -5026,6 +4159,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -5039,6 +4173,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -5052,6 +4187,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -5065,6 +4201,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -5078,6 +4215,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -5091,6 +4229,7 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -5104,6 +4243,7 @@ "cpu": [ "riscv64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -5117,6 +4257,7 @@ "cpu": [ "riscv64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -5130,6 +4271,7 @@ "cpu": [ "s390x" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -5143,6 +4285,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -5156,6 +4299,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -5169,6 +4313,7 @@ "cpu": [ "wasm32" ], + "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -5185,6 +4330,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -5198,6 +4344,7 @@ "cpu": [ "ia32" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -5211,6 +4358,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -5462,35 +4610,6 @@ "node": ">= 6.0.0" } }, - "node_modules/ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "type-fest": "^0.21.3" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ansi-escapes/node_modules/type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "license": "(MIT OR CC0-1.0)", - "peer": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/ansi-regex": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", @@ -5536,6 +4655,7 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, "license": "ISC", "dependencies": { "normalize-path": "^3.0.0", @@ -5549,6 +4669,7 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, "license": "MIT", "engines": { "node": ">=8.6" @@ -5557,10 +4678,6 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/api": { - "resolved": "apps/api", - "link": true - }, "node_modules/app-root-path": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/app-root-path/-/app-root-path-3.1.0.tgz", @@ -5914,105 +5031,6 @@ "node": ">= 0.4" } }, - "node_modules/babel-jest": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.2.0.tgz", - "integrity": "sha512-0YiBEOxWqKkSQWL9nNGGEgndoeL0ZpWrbLMNL5u/Kaxrli3Eaxlt3ZtIDktEvXt4L/R9r3ODr2zKwGM/2BjxVw==", - "license": "MIT", - "peer": true, - "dependencies": { - "@jest/transform": "30.2.0", - "@types/babel__core": "^7.20.5", - "babel-plugin-istanbul": "^7.0.1", - "babel-preset-jest": "30.2.0", - "chalk": "^4.1.2", - "graceful-fs": "^4.2.11", - "slash": "^3.0.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.11.0 || ^8.0.0-0" - } - }, - "node_modules/babel-plugin-istanbul": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-7.0.1.tgz", - "integrity": "sha512-D8Z6Qm8jCvVXtIRkBnqNHX0zJ37rQcFJ9u8WOS6tkYOsRdHBzypCstaxWiu5ZIlqQtviRYbgnRLSoCEvjqcqbA==", - "license": "BSD-3-Clause", - "peer": true, - "workspaces": [ - "test/babel-8" - ], - "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.3", - "istanbul-lib-instrument": "^6.0.2", - "test-exclude": "^6.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/babel-plugin-jest-hoist": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-30.2.0.tgz", - "integrity": "sha512-ftzhzSGMUnOzcCXd6WHdBGMyuwy15Wnn0iyyWGKgBDLxf9/s5ABuraCSpBX2uG0jUg4rqJnxsLc5+oYBqoxVaA==", - "license": "MIT", - "peer": true, - "dependencies": { - "@types/babel__core": "^7.20.5" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/babel-preset-current-node-syntax": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz", - "integrity": "sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==", - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/plugin-syntax-class-properties": "^7.12.13", - "@babel/plugin-syntax-class-static-block": "^7.14.5", - "@babel/plugin-syntax-import-attributes": "^7.24.7", - "@babel/plugin-syntax-import-meta": "^7.10.4", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.10.4", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5", - "@babel/plugin-syntax-top-level-await": "^7.14.5" - }, - "peerDependencies": { - "@babel/core": "^7.0.0 || ^8.0.0-0" - } - }, - "node_modules/babel-preset-jest": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-30.2.0.tgz", - "integrity": "sha512-US4Z3NOieAQumwFnYdUWKvUKh8+YSnS/gB3t6YBiz0bskpu7Pine8pPCheNxlPEW4wnUkma2a94YuW2q3guvCQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "babel-plugin-jest-hoist": "30.2.0", - "babel-preset-current-node-syntax": "^1.2.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.11.0 || ^8.0.0-beta.1" - } - }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -6243,6 +5261,7 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, "license": "MIT", "dependencies": { "fill-range": "^7.1.1" @@ -6284,28 +5303,6 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, - "node_modules/bs-logger": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", - "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", - "license": "MIT", - "dependencies": { - "fast-json-stable-stringify": "2.x" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/bser": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", - "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "node-int64": "^0.4.0" - } - }, "node_modules/buffer-crc32": { "version": "0.2.13", "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", @@ -6428,21 +5425,12 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=6" } }, - "node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=6" - } - }, "node_modules/camelcase-css": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", @@ -6523,16 +5511,6 @@ "node": ">=8" } }, - "node_modules/char-regex": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", - "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=10" - } - }, "node_modules/cheerio": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.1.2.tgz", @@ -6615,28 +5593,6 @@ "devtools-protocol": "*" } }, - "node_modules/ci-info": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.3.1.tgz", - "integrity": "sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/cjs-module-lexer": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.1.1.tgz", - "integrity": "sha512-+CmxIZ/L2vNcEfvNtLdU0ZQ6mbq3FZnwAP2PPTiKP+1QOoKwlKlPgb8UKV0Dds7QVaMnHm+FwSft2VB0s/SLjQ==", - "license": "MIT", - "peer": true - }, "node_modules/class-transformer": { "version": "0.5.1", "resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz", @@ -6735,24 +5691,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", - "license": "MIT", - "peer": true, - "engines": { - "iojs": ">= 1.0.0", - "node": ">= 0.12.0" - } - }, - "node_modules/collect-v8-coverage": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.3.tgz", - "integrity": "sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw==", - "license": "MIT", - "peer": true - }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -7260,16 +6198,6 @@ "node": ">=8" } }, - "node_modules/detect-newline": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", - "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=8" - } - }, "node_modules/detect-node": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", @@ -7524,19 +6452,6 @@ "undici-types": "~6.21.0" } }, - "node_modules/emittery": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", - "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sindresorhus/emittery?sponsor=1" - } - }, "node_modules/emoji-regex": { "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", @@ -7614,6 +6529,7 @@ "version": "1.3.4", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "dev": true, "license": "MIT", "dependencies": { "is-arrayish": "^0.2.1" @@ -8545,77 +7461,6 @@ "bare-events": "^2.7.0" } }, - "node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "license": "MIT", - "peer": true, - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/execa/node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/execa/node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "license": "ISC", - "peer": true - }, - "node_modules/exit-x": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/exit-x/-/exit-x-0.2.2.tgz", - "integrity": "sha512-+I6B/IkJc1o/2tiURyz/ivu/O0nKNEArIUB5O7zBrlDVJr22SCLH3xTeEry428LvFhRzIA1g8izguxJ/gbNcVQ==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/expect": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/expect/-/expect-30.2.0.tgz", - "integrity": "sha512-u/feCi0GPsI+988gU2FLcsHyAHTU0MX1Wg68NhAnN7z/+C5wqG+CY8J53N9ioe8RXgaoz0nBR/TYMf3AycUuPw==", - "license": "MIT", - "dependencies": { - "@jest/expect-utils": "30.2.0", - "@jest/get-type": "30.1.0", - "jest-matcher-utils": "30.2.0", - "jest-message-util": "30.2.0", - "jest-mock": "30.2.0", - "jest-util": "30.2.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, "node_modules/expect-type": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", @@ -8747,6 +7592,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, "license": "MIT" }, "node_modules/fast-levenshtein": { @@ -8772,16 +7618,6 @@ "reusify": "^1.0.4" } }, - "node_modules/fb-watchman": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", - "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "bser": "2.1.1" - } - }, "node_modules/fd-slicer": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", @@ -8875,6 +7711,7 @@ "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" @@ -9222,16 +8059,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/get-package-type": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", - "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=8.0.0" - } - }, "node_modules/get-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", @@ -9517,6 +8344,7 @@ "version": "4.7.8", "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", + "dev": true, "license": "MIT", "dependencies": { "minimist": "^1.2.5", @@ -9695,13 +8523,6 @@ "node": ">=12" } }, - "node_modules/html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "license": "MIT", - "peer": true - }, "node_modules/htmlparser2": { "version": "10.0.0", "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.0.0.tgz", @@ -9799,16 +8620,6 @@ "node": ">= 6" } }, - "node_modules/human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "license": "Apache-2.0", - "peer": true, - "engines": { - "node": ">=10.17.0" - } - }, "node_modules/husky": { "version": "9.1.7", "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz", @@ -9884,30 +8695,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/import-local": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", - "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", - "license": "MIT", - "peer": true, - "dependencies": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - }, - "bin": { - "import-local-fixture": "fixtures/cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.8.19" @@ -10018,6 +8810,7 @@ "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, "license": "MIT" }, "node_modules/is-async-function": { @@ -10209,16 +9002,6 @@ "node": ">=8" } }, - "node_modules/is-generator-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", - "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=6" - } - }, "node_modules/is-generator-function": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", @@ -10299,6 +9082,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.12.0" @@ -10402,6 +9186,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -10527,90 +9312,6 @@ "node": ">=0.10.0" } }, - "node_modules/istanbul-lib-coverage": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", - "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", - "license": "BSD-3-Clause", - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-instrument": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", - "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", - "license": "BSD-3-Clause", - "peer": true, - "dependencies": { - "@babel/core": "^7.23.9", - "@babel/parser": "^7.23.9", - "@istanbuljs/schema": "^0.1.3", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^7.5.4" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-lib-report": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", - "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", - "license": "BSD-3-Clause", - "peer": true, - "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^4.0.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-lib-report/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "license": "MIT", - "peer": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-source-maps": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", - "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", - "license": "BSD-3-Clause", - "peer": true, - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.23", - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-reports": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", - "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", - "license": "BSD-3-Clause", - "peer": true, - "dependencies": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/iterare": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/iterare/-/iterare-1.2.1.tgz", @@ -10653,894 +9354,6 @@ "@pkgjs/parseargs": "^0.11.0" } }, - "node_modules/jest": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest/-/jest-30.2.0.tgz", - "integrity": "sha512-F26gjC0yWN8uAA5m5Ss8ZQf5nDHWGlN/xWZIh8S5SRbsEKBovwZhxGd6LJlbZYxBgCYOtreSUyb8hpXyGC5O4A==", - "license": "MIT", - "peer": true, - "dependencies": { - "@jest/core": "30.2.0", - "@jest/types": "30.2.0", - "import-local": "^3.2.0", - "jest-cli": "30.2.0" - }, - "bin": { - "jest": "bin/jest.js" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/jest-changed-files": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-30.2.0.tgz", - "integrity": "sha512-L8lR1ChrRnSdfeOvTrwZMlnWV8G/LLjQ0nG9MBclwWZidA2N5FviRki0Bvh20WRMOX31/JYvzdqTJrk5oBdydQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "execa": "^5.1.1", - "jest-util": "30.2.0", - "p-limit": "^3.1.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-circus": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-30.2.0.tgz", - "integrity": "sha512-Fh0096NC3ZkFx05EP2OXCxJAREVxj1BcW/i6EWqqymcgYKWjyyDpral3fMxVcHXg6oZM7iULer9wGRFvfpl+Tg==", - "license": "MIT", - "peer": true, - "dependencies": { - "@jest/environment": "30.2.0", - "@jest/expect": "30.2.0", - "@jest/test-result": "30.2.0", - "@jest/types": "30.2.0", - "@types/node": "*", - "chalk": "^4.1.2", - "co": "^4.6.0", - "dedent": "^1.6.0", - "is-generator-fn": "^2.1.0", - "jest-each": "30.2.0", - "jest-matcher-utils": "30.2.0", - "jest-message-util": "30.2.0", - "jest-runtime": "30.2.0", - "jest-snapshot": "30.2.0", - "jest-util": "30.2.0", - "p-limit": "^3.1.0", - "pretty-format": "30.2.0", - "pure-rand": "^7.0.0", - "slash": "^3.0.0", - "stack-utils": "^2.0.6" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-circus/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-circus/node_modules/pretty-format": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.2.0.tgz", - "integrity": "sha512-9uBdv/B4EefsuAL+pWqueZyZS2Ba+LxfFeQ9DN14HU4bN8bhaxKdkpjpB6fs9+pSjIBu+FXQHImEg8j/Lw0+vA==", - "license": "MIT", - "peer": true, - "dependencies": { - "@jest/schemas": "30.0.5", - "ansi-styles": "^5.2.0", - "react-is": "^18.3.1" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-circus/node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "license": "MIT", - "peer": true - }, - "node_modules/jest-cli": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-30.2.0.tgz", - "integrity": "sha512-Os9ukIvADX/A9sLt6Zse3+nmHtHaE6hqOsjQtNiugFTbKRHYIYtZXNGNK9NChseXy7djFPjndX1tL0sCTlfpAA==", - "license": "MIT", - "peer": true, - "dependencies": { - "@jest/core": "30.2.0", - "@jest/test-result": "30.2.0", - "@jest/types": "30.2.0", - "chalk": "^4.1.2", - "exit-x": "^0.2.2", - "import-local": "^3.2.0", - "jest-config": "30.2.0", - "jest-util": "30.2.0", - "jest-validate": "30.2.0", - "yargs": "^17.7.2" - }, - "bin": { - "jest": "bin/jest.js" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/jest-config": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-30.2.0.tgz", - "integrity": "sha512-g4WkyzFQVWHtu6uqGmQR4CQxz/CH3yDSlhzXMWzNjDx843gYjReZnMRanjRCq5XZFuQrGDxgUaiYWE8BRfVckA==", - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/core": "^7.27.4", - "@jest/get-type": "30.1.0", - "@jest/pattern": "30.0.1", - "@jest/test-sequencer": "30.2.0", - "@jest/types": "30.2.0", - "babel-jest": "30.2.0", - "chalk": "^4.1.2", - "ci-info": "^4.2.0", - "deepmerge": "^4.3.1", - "glob": "^10.3.10", - "graceful-fs": "^4.2.11", - "jest-circus": "30.2.0", - "jest-docblock": "30.2.0", - "jest-environment-node": "30.2.0", - "jest-regex-util": "30.0.1", - "jest-resolve": "30.2.0", - "jest-runner": "30.2.0", - "jest-util": "30.2.0", - "jest-validate": "30.2.0", - "micromatch": "^4.0.8", - "parse-json": "^5.2.0", - "pretty-format": "30.2.0", - "slash": "^3.0.0", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "peerDependencies": { - "@types/node": "*", - "esbuild-register": ">=3.4.0", - "ts-node": ">=9.0.0" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "esbuild-register": { - "optional": true - }, - "ts-node": { - "optional": true - } - } - }, - "node_modules/jest-config/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-config/node_modules/pretty-format": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.2.0.tgz", - "integrity": "sha512-9uBdv/B4EefsuAL+pWqueZyZS2Ba+LxfFeQ9DN14HU4bN8bhaxKdkpjpB6fs9+pSjIBu+FXQHImEg8j/Lw0+vA==", - "license": "MIT", - "peer": true, - "dependencies": { - "@jest/schemas": "30.0.5", - "ansi-styles": "^5.2.0", - "react-is": "^18.3.1" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-config/node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "license": "MIT", - "peer": true - }, - "node_modules/jest-diff": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.2.0.tgz", - "integrity": "sha512-dQHFo3Pt4/NLlG5z4PxZ/3yZTZ1C7s9hveiOj+GCN+uT109NC2QgsoVZsVOAvbJ3RgKkvyLGXZV9+piDpWbm6A==", - "license": "MIT", - "dependencies": { - "@jest/diff-sequences": "30.0.1", - "@jest/get-type": "30.1.0", - "chalk": "^4.1.2", - "pretty-format": "30.2.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-diff/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-diff/node_modules/pretty-format": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.2.0.tgz", - "integrity": "sha512-9uBdv/B4EefsuAL+pWqueZyZS2Ba+LxfFeQ9DN14HU4bN8bhaxKdkpjpB6fs9+pSjIBu+FXQHImEg8j/Lw0+vA==", - "license": "MIT", - "dependencies": { - "@jest/schemas": "30.0.5", - "ansi-styles": "^5.2.0", - "react-is": "^18.3.1" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-diff/node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "license": "MIT" - }, - "node_modules/jest-docblock": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-30.2.0.tgz", - "integrity": "sha512-tR/FFgZKS1CXluOQzZvNH3+0z9jXr3ldGSD8bhyuxvlVUwbeLOGynkunvlTMxchC5urrKndYiwCFC0DLVjpOCA==", - "license": "MIT", - "peer": true, - "dependencies": { - "detect-newline": "^3.1.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-each": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-30.2.0.tgz", - "integrity": "sha512-lpWlJlM7bCUf1mfmuqTA8+j2lNURW9eNafOy99knBM01i5CQeY5UH1vZjgT9071nDJac1M4XsbyI44oNOdhlDQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "@jest/get-type": "30.1.0", - "@jest/types": "30.2.0", - "chalk": "^4.1.2", - "jest-util": "30.2.0", - "pretty-format": "30.2.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-each/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-each/node_modules/pretty-format": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.2.0.tgz", - "integrity": "sha512-9uBdv/B4EefsuAL+pWqueZyZS2Ba+LxfFeQ9DN14HU4bN8bhaxKdkpjpB6fs9+pSjIBu+FXQHImEg8j/Lw0+vA==", - "license": "MIT", - "peer": true, - "dependencies": { - "@jest/schemas": "30.0.5", - "ansi-styles": "^5.2.0", - "react-is": "^18.3.1" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-each/node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "license": "MIT", - "peer": true - }, - "node_modules/jest-environment-node": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-30.2.0.tgz", - "integrity": "sha512-ElU8v92QJ9UrYsKrxDIKCxu6PfNj4Hdcktcn0JX12zqNdqWHB0N+hwOnnBBXvjLd2vApZtuLUGs1QSY+MsXoNA==", - "license": "MIT", - "peer": true, - "dependencies": { - "@jest/environment": "30.2.0", - "@jest/fake-timers": "30.2.0", - "@jest/types": "30.2.0", - "@types/node": "*", - "jest-mock": "30.2.0", - "jest-util": "30.2.0", - "jest-validate": "30.2.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-haste-map": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.2.0.tgz", - "integrity": "sha512-sQA/jCb9kNt+neM0anSj6eZhLZUIhQgwDt7cPGjumgLM4rXsfb9kpnlacmvZz3Q5tb80nS+oG/if+NBKrHC+Xw==", - "license": "MIT", - "peer": true, - "dependencies": { - "@jest/types": "30.2.0", - "@types/node": "*", - "anymatch": "^3.1.3", - "fb-watchman": "^2.0.2", - "graceful-fs": "^4.2.11", - "jest-regex-util": "30.0.1", - "jest-util": "30.2.0", - "jest-worker": "30.2.0", - "micromatch": "^4.0.8", - "walker": "^1.0.8" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "optionalDependencies": { - "fsevents": "^2.3.3" - } - }, - "node_modules/jest-leak-detector": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-30.2.0.tgz", - "integrity": "sha512-M6jKAjyzjHG0SrQgwhgZGy9hFazcudwCNovY/9HPIicmNSBuockPSedAP9vlPK6ONFJ1zfyH/M2/YYJxOz5cdQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "@jest/get-type": "30.1.0", - "pretty-format": "30.2.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-leak-detector/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-leak-detector/node_modules/pretty-format": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.2.0.tgz", - "integrity": "sha512-9uBdv/B4EefsuAL+pWqueZyZS2Ba+LxfFeQ9DN14HU4bN8bhaxKdkpjpB6fs9+pSjIBu+FXQHImEg8j/Lw0+vA==", - "license": "MIT", - "peer": true, - "dependencies": { - "@jest/schemas": "30.0.5", - "ansi-styles": "^5.2.0", - "react-is": "^18.3.1" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-leak-detector/node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "license": "MIT", - "peer": true - }, - "node_modules/jest-matcher-utils": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.2.0.tgz", - "integrity": "sha512-dQ94Nq4dbzmUWkQ0ANAWS9tBRfqCrn0bV9AMYdOi/MHW726xn7eQmMeRTpX2ViC00bpNaWXq+7o4lIQ3AX13Hg==", - "license": "MIT", - "dependencies": { - "@jest/get-type": "30.1.0", - "chalk": "^4.1.2", - "jest-diff": "30.2.0", - "pretty-format": "30.2.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-matcher-utils/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-matcher-utils/node_modules/pretty-format": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.2.0.tgz", - "integrity": "sha512-9uBdv/B4EefsuAL+pWqueZyZS2Ba+LxfFeQ9DN14HU4bN8bhaxKdkpjpB6fs9+pSjIBu+FXQHImEg8j/Lw0+vA==", - "license": "MIT", - "dependencies": { - "@jest/schemas": "30.0.5", - "ansi-styles": "^5.2.0", - "react-is": "^18.3.1" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-matcher-utils/node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "license": "MIT" - }, - "node_modules/jest-message-util": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.2.0.tgz", - "integrity": "sha512-y4DKFLZ2y6DxTWD4cDe07RglV88ZiNEdlRfGtqahfbIjfsw1nMCPx49Uev4IA/hWn3sDKyAnSPwoYSsAEdcimw==", - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.27.1", - "@jest/types": "30.2.0", - "@types/stack-utils": "^2.0.3", - "chalk": "^4.1.2", - "graceful-fs": "^4.2.11", - "micromatch": "^4.0.8", - "pretty-format": "30.2.0", - "slash": "^3.0.0", - "stack-utils": "^2.0.6" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-message-util/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-message-util/node_modules/pretty-format": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.2.0.tgz", - "integrity": "sha512-9uBdv/B4EefsuAL+pWqueZyZS2Ba+LxfFeQ9DN14HU4bN8bhaxKdkpjpB6fs9+pSjIBu+FXQHImEg8j/Lw0+vA==", - "license": "MIT", - "dependencies": { - "@jest/schemas": "30.0.5", - "ansi-styles": "^5.2.0", - "react-is": "^18.3.1" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-message-util/node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "license": "MIT" - }, - "node_modules/jest-mock": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.2.0.tgz", - "integrity": "sha512-JNNNl2rj4b5ICpmAcq+WbLH83XswjPbjH4T7yvGzfAGCPh1rw+xVNbtk+FnRslvt9lkCcdn9i1oAoKUuFsOxRw==", - "license": "MIT", - "dependencies": { - "@jest/types": "30.2.0", - "@types/node": "*", - "jest-util": "30.2.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-pnp-resolver": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", - "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=6" - }, - "peerDependencies": { - "jest-resolve": "*" - }, - "peerDependenciesMeta": { - "jest-resolve": { - "optional": true - } - } - }, - "node_modules/jest-regex-util": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.0.1.tgz", - "integrity": "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==", - "license": "MIT", - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-resolve": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-30.2.0.tgz", - "integrity": "sha512-TCrHSxPlx3tBY3hWNtRQKbtgLhsXa1WmbJEqBlTBrGafd5fiQFByy2GNCEoGR+Tns8d15GaL9cxEzKOO3GEb2A==", - "license": "MIT", - "peer": true, - "dependencies": { - "chalk": "^4.1.2", - "graceful-fs": "^4.2.11", - "jest-haste-map": "30.2.0", - "jest-pnp-resolver": "^1.2.3", - "jest-util": "30.2.0", - "jest-validate": "30.2.0", - "slash": "^3.0.0", - "unrs-resolver": "^1.7.11" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-resolve-dependencies": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-30.2.0.tgz", - "integrity": "sha512-xTOIGug/0RmIe3mmCqCT95yO0vj6JURrn1TKWlNbhiAefJRWINNPgwVkrVgt/YaerPzY3iItufd80v3lOrFJ2w==", - "license": "MIT", - "peer": true, - "dependencies": { - "jest-regex-util": "30.0.1", - "jest-snapshot": "30.2.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-runner": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-30.2.0.tgz", - "integrity": "sha512-PqvZ2B2XEyPEbclp+gV6KO/F1FIFSbIwewRgmROCMBo/aZ6J1w8Qypoj2pEOcg3G2HzLlaP6VUtvwCI8dM3oqQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "@jest/console": "30.2.0", - "@jest/environment": "30.2.0", - "@jest/test-result": "30.2.0", - "@jest/transform": "30.2.0", - "@jest/types": "30.2.0", - "@types/node": "*", - "chalk": "^4.1.2", - "emittery": "^0.13.1", - "exit-x": "^0.2.2", - "graceful-fs": "^4.2.11", - "jest-docblock": "30.2.0", - "jest-environment-node": "30.2.0", - "jest-haste-map": "30.2.0", - "jest-leak-detector": "30.2.0", - "jest-message-util": "30.2.0", - "jest-resolve": "30.2.0", - "jest-runtime": "30.2.0", - "jest-util": "30.2.0", - "jest-watcher": "30.2.0", - "jest-worker": "30.2.0", - "p-limit": "^3.1.0", - "source-map-support": "0.5.13" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-runner/node_modules/source-map-support": { - "version": "0.5.13", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", - "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", - "license": "MIT", - "peer": true, - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/jest-runtime": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-30.2.0.tgz", - "integrity": "sha512-p1+GVX/PJqTucvsmERPMgCPvQJpFt4hFbM+VN3n8TMo47decMUcJbt+rgzwrEme0MQUA/R+1de2axftTHkKckg==", - "license": "MIT", - "peer": true, - "dependencies": { - "@jest/environment": "30.2.0", - "@jest/fake-timers": "30.2.0", - "@jest/globals": "30.2.0", - "@jest/source-map": "30.0.1", - "@jest/test-result": "30.2.0", - "@jest/transform": "30.2.0", - "@jest/types": "30.2.0", - "@types/node": "*", - "chalk": "^4.1.2", - "cjs-module-lexer": "^2.1.0", - "collect-v8-coverage": "^1.0.2", - "glob": "^10.3.10", - "graceful-fs": "^4.2.11", - "jest-haste-map": "30.2.0", - "jest-message-util": "30.2.0", - "jest-mock": "30.2.0", - "jest-regex-util": "30.0.1", - "jest-resolve": "30.2.0", - "jest-snapshot": "30.2.0", - "jest-util": "30.2.0", - "slash": "^3.0.0", - "strip-bom": "^4.0.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-snapshot": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.2.0.tgz", - "integrity": "sha512-5WEtTy2jXPFypadKNpbNkZ72puZCa6UjSr/7djeecHWOu7iYhSXSnHScT8wBz3Rn8Ena5d5RYRcsyKIeqG1IyA==", - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/core": "^7.27.4", - "@babel/generator": "^7.27.5", - "@babel/plugin-syntax-jsx": "^7.27.1", - "@babel/plugin-syntax-typescript": "^7.27.1", - "@babel/types": "^7.27.3", - "@jest/expect-utils": "30.2.0", - "@jest/get-type": "30.1.0", - "@jest/snapshot-utils": "30.2.0", - "@jest/transform": "30.2.0", - "@jest/types": "30.2.0", - "babel-preset-current-node-syntax": "^1.2.0", - "chalk": "^4.1.2", - "expect": "30.2.0", - "graceful-fs": "^4.2.11", - "jest-diff": "30.2.0", - "jest-matcher-utils": "30.2.0", - "jest-message-util": "30.2.0", - "jest-util": "30.2.0", - "pretty-format": "30.2.0", - "semver": "^7.7.2", - "synckit": "^0.11.8" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-snapshot/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-snapshot/node_modules/pretty-format": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.2.0.tgz", - "integrity": "sha512-9uBdv/B4EefsuAL+pWqueZyZS2Ba+LxfFeQ9DN14HU4bN8bhaxKdkpjpB6fs9+pSjIBu+FXQHImEg8j/Lw0+vA==", - "license": "MIT", - "peer": true, - "dependencies": { - "@jest/schemas": "30.0.5", - "ansi-styles": "^5.2.0", - "react-is": "^18.3.1" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-snapshot/node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "license": "MIT", - "peer": true - }, - "node_modules/jest-snapshot/node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", - "license": "ISC", - "peer": true, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/jest-util": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.2.0.tgz", - "integrity": "sha512-QKNsM0o3Xe6ISQU869e+DhG+4CK/48aHYdJZGlFQVTjnbvgpcKyxpzk29fGiO7i/J8VENZ+d2iGnSsvmuHywlA==", - "license": "MIT", - "dependencies": { - "@jest/types": "30.2.0", - "@types/node": "*", - "chalk": "^4.1.2", - "ci-info": "^4.2.0", - "graceful-fs": "^4.2.11", - "picomatch": "^4.0.2" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-validate": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-30.2.0.tgz", - "integrity": "sha512-FBGWi7dP2hpdi8nBoWxSsLvBFewKAg0+uSQwBaof4Y4DPgBabXgpSYC5/lR7VmnIlSpASmCi/ntRWPbv7089Pw==", - "license": "MIT", - "peer": true, - "dependencies": { - "@jest/get-type": "30.1.0", - "@jest/types": "30.2.0", - "camelcase": "^6.3.0", - "chalk": "^4.1.2", - "leven": "^3.1.0", - "pretty-format": "30.2.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-validate/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-validate/node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/jest-validate/node_modules/pretty-format": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.2.0.tgz", - "integrity": "sha512-9uBdv/B4EefsuAL+pWqueZyZS2Ba+LxfFeQ9DN14HU4bN8bhaxKdkpjpB6fs9+pSjIBu+FXQHImEg8j/Lw0+vA==", - "license": "MIT", - "peer": true, - "dependencies": { - "@jest/schemas": "30.0.5", - "ansi-styles": "^5.2.0", - "react-is": "^18.3.1" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-validate/node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "license": "MIT", - "peer": true - }, - "node_modules/jest-watcher": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-30.2.0.tgz", - "integrity": "sha512-PYxa28dxJ9g777pGm/7PrbnMeA0Jr7osHP9bS7eJy9DuAjMgdGtxgf0uKMyoIsTWAkIbUW5hSDdJ3urmgXBqxg==", - "license": "MIT", - "peer": true, - "dependencies": { - "@jest/test-result": "30.2.0", - "@jest/types": "30.2.0", - "@types/node": "*", - "ansi-escapes": "^4.3.2", - "chalk": "^4.1.2", - "emittery": "^0.13.1", - "jest-util": "30.2.0", - "string-length": "^4.0.2" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-worker": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.2.0.tgz", - "integrity": "sha512-0Q4Uk8WF7BUwqXHuAjc23vmopWJw5WH7w2tqBoUOZpOjW/ZnR44GXXd1r82RvnmI2GZge3ivrYXk/BE2+VtW2g==", - "license": "MIT", - "peer": true, - "dependencies": { - "@types/node": "*", - "@ungap/structured-clone": "^1.3.0", - "jest-util": "30.2.0", - "merge-stream": "^2.0.0", - "supports-color": "^8.1.1" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, "node_modules/jiti": { "version": "1.21.7", "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", @@ -11670,6 +9483,7 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, "license": "MIT" }, "node_modules/json-stable-stringify-without-jsonify": { @@ -11783,16 +9597,6 @@ "node": ">=0.10.0" } }, - "node_modules/leven": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=6" - } - }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -11830,6 +9634,7 @@ "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, "license": "MIT" }, "node_modules/load-esm": { @@ -11874,12 +9679,6 @@ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "license": "MIT" }, - "node_modules/lodash.memoize": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", - "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", - "license": "MIT" - }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -11980,38 +9779,13 @@ "@jridgewell/sourcemap-codec": "^1.5.5" } }, - "node_modules/make-dir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", - "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", - "license": "MIT", - "peer": true, - "dependencies": { - "semver": "^7.5.3" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "devOptional": true, "license": "ISC" }, - "node_modules/makeerror": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", - "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", - "license": "BSD-3-Clause", - "peer": true, - "dependencies": { - "tmpl": "1.0.5" - } - }, "node_modules/matcher": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz", @@ -12066,13 +9840,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "license": "MIT", - "peer": true - }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -12096,6 +9863,7 @@ "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, "license": "MIT", "dependencies": { "braces": "^3.0.3", @@ -12109,6 +9877,7 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, "license": "MIT", "engines": { "node": ">=8.6" @@ -12151,16 +9920,6 @@ "node": ">= 0.6" } }, - "node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=6" - } - }, "node_modules/mimic-response": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", @@ -12349,6 +10108,7 @@ "version": "0.3.4", "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz", "integrity": "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==", + "dev": true, "license": "MIT", "bin": { "napi-postinstall": "lib/cli.js" @@ -12364,6 +10124,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, "license": "MIT" }, "node_modules/negotiator": { @@ -12379,6 +10140,7 @@ "version": "2.6.2", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true, "license": "MIT" }, "node_modules/netmask": { @@ -12543,13 +10305,6 @@ "node-gyp-build-test": "build-test.js" } }, - "node_modules/node-int64": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", - "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", - "license": "MIT", - "peer": true - }, "node_modules/node-releases": { "version": "2.0.27", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", @@ -12575,6 +10330,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -12592,19 +10348,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "license": "MIT", - "peer": true, - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/nth-check": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", @@ -12788,22 +10531,6 @@ "wrappy": "1" } }, - "node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "license": "MIT", - "peer": true, - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -12853,6 +10580,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, "license": "MIT", "dependencies": { "yocto-queue": "^0.1.0" @@ -12880,16 +10608,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=6" - } - }, "node_modules/pac-proxy-agent": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.2.0.tgz", @@ -12993,6 +10711,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.0.0", @@ -13073,6 +10792,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -13275,80 +10995,12 @@ "version": "4.0.7", "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, "license": "MIT", "engines": { "node": ">= 6" } }, - "node_modules/pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "find-up": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/pkg-dir/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "license": "MIT", - "peer": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/pkg-dir/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "license": "MIT", - "peer": true, - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/pkg-dir/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "license": "MIT", - "peer": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/pkg-dir/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "license": "MIT", - "peer": true, - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/playwright": { "version": "1.57.0", "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.57.0.tgz", @@ -14055,23 +11707,6 @@ } } }, - "node_modules/pure-rand": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-7.0.1.tgz", - "integrity": "sha512-oTUZM/NAZS8p7ANR3SHh30kXB+zK2r2BPcEn/awJIbOvq82WoMN4p62AWWp3Hhw50G0xMsw1mhIBLqHw64EcNQ==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/dubzzz" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fast-check" - } - ], - "license": "MIT", - "peer": true - }, "node_modules/qs": { "version": "6.13.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", @@ -14442,29 +12077,6 @@ "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", "license": "MIT" }, - "node_modules/resolve-cwd": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", - "license": "MIT", - "peer": true, - "dependencies": { - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve-cwd/node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=8" - } - }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -14758,6 +12370,7 @@ "version": "7.7.1", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "devOptional": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -15176,6 +12789,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -15232,6 +12846,7 @@ "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "devOptional": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -15332,27 +12947,6 @@ "dev": true, "license": "MIT" }, - "node_modules/stack-utils": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", - "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", - "license": "MIT", - "dependencies": { - "escape-string-regexp": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/stack-utils/node_modules/escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/stackback": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", @@ -15435,20 +13029,6 @@ "node": ">=0.6.19" } }, - "node_modules/string-length": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", - "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "char-regex": "^1.0.2", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", @@ -15646,26 +13226,6 @@ "node": ">=8" } }, - "node_modules/strip-bom": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", - "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=6" - } - }, "node_modules/strip-indent": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", @@ -15683,6 +13243,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -15779,6 +13340,7 @@ "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, "license": "MIT", "dependencies": { "has-flag": "^4.0.0" @@ -15819,22 +13381,6 @@ "dev": true, "license": "MIT" }, - "node_modules/synckit": { - "version": "0.11.11", - "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.11.tgz", - "integrity": "sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==", - "license": "MIT", - "peer": true, - "dependencies": { - "@pkgr/core": "^0.2.9" - }, - "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/synckit" - } - }, "node_modules/tar-fs": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.1.tgz", @@ -15874,43 +13420,6 @@ } } }, - "node_modules/test-exclude": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", - "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", - "license": "ISC", - "peer": true, - "dependencies": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/test-exclude/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "license": "ISC", - "peer": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/text-decoder": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.3.tgz", @@ -16014,13 +13523,6 @@ "node": ">=14.0.0" } }, - "node_modules/tmpl": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", - "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", - "license": "BSD-3-Clause", - "peer": true - }, "node_modules/to-buffer": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.2.2.tgz", @@ -16039,6 +13541,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, "license": "MIT", "dependencies": { "is-number": "^7.0.0" @@ -16160,70 +13663,6 @@ "dev": true, "license": "Apache-2.0" }, - "node_modules/ts-jest": { - "version": "29.4.6", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.6.tgz", - "integrity": "sha512-fSpWtOO/1AjSNQguk43hb/JCo16oJDnMJf3CdEGNkqsEX3t0KX96xvyX1D7PfLCpVoKu4MfVrqUkFyblYoY4lA==", - "license": "MIT", - "dependencies": { - "bs-logger": "^0.2.6", - "fast-json-stable-stringify": "^2.1.0", - "handlebars": "^4.7.8", - "json5": "^2.2.3", - "lodash.memoize": "^4.1.2", - "make-error": "^1.3.6", - "semver": "^7.7.3", - "type-fest": "^4.41.0", - "yargs-parser": "^21.1.1" - }, - "bin": { - "ts-jest": "cli.js" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0" - }, - "peerDependencies": { - "@babel/core": ">=7.0.0-beta.0 <8", - "@jest/transform": "^29.0.0 || ^30.0.0", - "@jest/types": "^29.0.0 || ^30.0.0", - "babel-jest": "^29.0.0 || ^30.0.0", - "jest": "^29.0.0 || ^30.0.0", - "jest-util": "^29.0.0 || ^30.0.0", - "typescript": ">=4.3 <6" - }, - "peerDependenciesMeta": { - "@babel/core": { - "optional": true - }, - "@jest/transform": { - "optional": true - }, - "@jest/types": { - "optional": true - }, - "babel-jest": { - "optional": true - }, - "esbuild": { - "optional": true - }, - "jest-util": { - "optional": true - } - } - }, - "node_modules/ts-jest/node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/ts-node": { "version": "10.9.2", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", @@ -16852,20 +14291,11 @@ "node": ">= 0.8.0" } }, - "node_modules/type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=4" - } - }, "node_modules/type-fest": { "version": "4.41.0", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "dev": true, "license": "(MIT OR CC0-1.0)", "engines": { "node": ">=16" @@ -17119,6 +14549,7 @@ "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "devOptional": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -17132,6 +14563,7 @@ "version": "3.19.3", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", + "dev": true, "license": "BSD-2-Clause", "optional": true, "bin": { @@ -17241,6 +14673,7 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.11.1.tgz", "integrity": "sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==", + "dev": true, "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -17374,21 +14807,6 @@ "devOptional": true, "license": "MIT" }, - "node_modules/v8-to-istanbul": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", - "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", - "license": "ISC", - "peer": true, - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.12", - "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^2.0.0" - }, - "engines": { - "node": ">=10.12.0" - } - }, "node_modules/validate-npm-package-license": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", @@ -17583,16 +15001,6 @@ "node": ">=14" } }, - "node_modules/walker": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", - "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "makeerror": "1.0.12" - } - }, "node_modules/webdriver-bidi-protocol": { "version": "0.3.9", "resolved": "https://registry.npmjs.org/webdriver-bidi-protocol/-/webdriver-bidi-protocol-0.3.9.tgz", @@ -17793,6 +15201,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", + "dev": true, "license": "MIT" }, "node_modules/wrap-ansi-cjs": { @@ -17819,20 +15228,6 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "license": "ISC" }, - "node_modules/write-file-atomic": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", - "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", - "license": "ISC", - "peer": true, - "dependencies": { - "imurmurhash": "^0.1.4", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, "node_modules/ws": { "version": "8.18.3", "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", @@ -17972,6 +15367,7 @@ "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, "license": "MIT", "engines": { "node": ">=10"