145 lines
3.9 KiB
TypeScript
145 lines
3.9 KiB
TypeScript
import { randomUUID } from 'crypto';
|
|
import type { IEntity } from '@gridpilot/shared/domain';
|
|
import { StepId } from '../value-objects/StepId';
|
|
import { SessionState } from '../value-objects/SessionState';
|
|
import type { HostedSessionConfig } from '../types/HostedSessionConfig';
|
|
import { AutomationDomainError } from '../errors/AutomationDomainError';
|
|
|
|
export class AutomationSession implements IEntity<string> {
|
|
private readonly _id: string;
|
|
private _currentStep: StepId;
|
|
private _state: SessionState;
|
|
private readonly _config: HostedSessionConfig;
|
|
private _startedAt?: Date;
|
|
private _completedAt?: Date;
|
|
private _errorMessage?: string;
|
|
|
|
private constructor(
|
|
id: string,
|
|
currentStep: StepId,
|
|
state: SessionState,
|
|
config: HostedSessionConfig
|
|
) {
|
|
this._id = id;
|
|
this._currentStep = currentStep;
|
|
this._state = state;
|
|
this._config = config;
|
|
}
|
|
|
|
static create(config: HostedSessionConfig): AutomationSession {
|
|
if (!config.sessionName || config.sessionName.trim() === '') {
|
|
throw new AutomationDomainError('Session name cannot be empty');
|
|
}
|
|
if (!config.trackId || config.trackId.trim() === '') {
|
|
throw new AutomationDomainError('Track ID is required');
|
|
}
|
|
if (!config.carIds || config.carIds.length === 0) {
|
|
throw new AutomationDomainError('At least one car must be selected');
|
|
}
|
|
|
|
return new AutomationSession(
|
|
randomUUID(),
|
|
StepId.create(1),
|
|
SessionState.create('PENDING'),
|
|
config
|
|
);
|
|
}
|
|
|
|
get id(): string {
|
|
return this._id;
|
|
}
|
|
|
|
get currentStep(): StepId {
|
|
return this._currentStep;
|
|
}
|
|
|
|
get state(): SessionState {
|
|
return this._state;
|
|
}
|
|
|
|
get config(): HostedSessionConfig {
|
|
return this._config;
|
|
}
|
|
|
|
get startedAt(): Date | undefined {
|
|
return this._startedAt;
|
|
}
|
|
|
|
get completedAt(): Date | undefined {
|
|
return this._completedAt;
|
|
}
|
|
|
|
get errorMessage(): string | undefined {
|
|
return this._errorMessage;
|
|
}
|
|
|
|
start(): void {
|
|
if (!this._state.isPending()) {
|
|
throw new AutomationDomainError('Cannot start session that is not pending');
|
|
}
|
|
this._state = SessionState.create('IN_PROGRESS');
|
|
this._startedAt = new Date();
|
|
}
|
|
|
|
transitionToStep(targetStep: StepId): void {
|
|
if (!this._state.isInProgress()) {
|
|
throw new AutomationDomainError('Cannot transition steps when session is not in progress');
|
|
}
|
|
|
|
if (this._currentStep.equals(targetStep)) {
|
|
throw new AutomationDomainError('Already at this step');
|
|
}
|
|
|
|
if (targetStep.value < this._currentStep.value) {
|
|
throw new AutomationDomainError('Cannot move backward - steps must progress forward only');
|
|
}
|
|
|
|
if (targetStep.value !== this._currentStep.value + 1) {
|
|
throw new AutomationDomainError('Cannot skip steps - must transition sequentially');
|
|
}
|
|
|
|
this._currentStep = targetStep;
|
|
|
|
if (this._currentStep.isFinalStep()) {
|
|
this._state = SessionState.create('STOPPED_AT_STEP_18');
|
|
this._completedAt = new Date();
|
|
}
|
|
}
|
|
|
|
pause(): void {
|
|
if (!this._state.isInProgress()) {
|
|
throw new AutomationDomainError('Cannot pause session that is not in progress');
|
|
}
|
|
this._state = SessionState.create('PAUSED');
|
|
}
|
|
|
|
resume(): void {
|
|
if (this._state.value !== 'PAUSED') {
|
|
throw new AutomationDomainError('Cannot resume session that is not paused');
|
|
}
|
|
this._state = SessionState.create('IN_PROGRESS');
|
|
}
|
|
|
|
fail(errorMessage: string): void {
|
|
if (this._state.isTerminal()) {
|
|
throw new AutomationDomainError('Cannot fail terminal session');
|
|
}
|
|
this._state = SessionState.create('FAILED');
|
|
this._errorMessage = errorMessage;
|
|
this._completedAt = new Date();
|
|
}
|
|
|
|
isAtModalStep(): boolean {
|
|
return this._currentStep.isModalStep();
|
|
}
|
|
|
|
getElapsedTime(): number {
|
|
if (!this._startedAt) {
|
|
return 0;
|
|
}
|
|
|
|
const endTime = this._completedAt || new Date();
|
|
const elapsed = endTime.getTime() - this._startedAt.getTime();
|
|
return elapsed > 0 ? elapsed : 1;
|
|
}
|
|
} |