Files
boc/packages/domain/src/session/session.ts
T
Bernt fa0dbf5127 PR-001: Domain Model v1.0 — compile-only, zero dependencies
- @landvex/domain package with TypeScript strict mode
- 3 Aggregate Roots: FieldSession, Mission, DecisionCase
- Branded IDs, Value Objects, Domain Events, Invariants
- 19 unit tests for IDs, FieldSession, DecisionCase
- Zero runtime dependencies (only TypeScript + jest for tests)
- Separates Entity / Value Object / Aggregate Root
- README documents Three Rules of the domain

Definition of Done met:
- Compiles without errors
- Exports all domain types
- Unit tests for invariants and value objects
- No PostgreSQL, S3, Express, AI models, queues
2026-07-02 14:26:29 +00:00

74 lines
1.9 KiB
TypeScript

/**
* FieldSession - Aggregate Root
* Organizes field work. A pilot day produces many missions.
*
* Invariants:
* - Must have exactly one location
* - Must have a date
* - Can have zero or more missions
* - Cannot be completed if any mission is still uploading
*/
import { SessionId, MissionId } from '../common/ids';
import { SessionStatus } from '../common/enums';
import { GeoLocation } from '../common/value-objects';
import { InvariantViolationError } from '../common/errors';
export interface FieldSession {
readonly id: SessionId;
readonly location: GeoLocation;
readonly date: Date;
readonly status: SessionStatus;
readonly missionIds: MissionId[];
readonly createdAt: Date;
readonly updatedAt: Date;
}
export interface CreateSessionParams {
readonly id: SessionId;
readonly location: GeoLocation;
readonly date: Date;
}
export class FieldSessionFactory {
static create(params: CreateSessionParams): FieldSession {
if (!params.location) {
throw new InvariantViolationError('Session must have a location');
}
if (!params.date) {
throw new InvariantViolationError('Session must have a date');
}
return {
id: params.id,
location: params.location,
date: params.date,
status: SessionStatus.PLANNED,
missionIds: [],
createdAt: new Date(),
updatedAt: new Date()
};
}
static addMission(session: FieldSession, missionId: MissionId): FieldSession {
return {
...session,
missionIds: [...session.missionIds, missionId],
status: SessionStatus.ACTIVE,
updatedAt: new Date()
};
}
static complete(session: FieldSession): FieldSession {
if (session.status !== SessionStatus.ACTIVE) {
throw new InvariantViolationError('Cannot complete session that is not active');
}
return {
...session,
status: SessionStatus.COMPLETED,
updatedAt: new Date()
};
}
}