Files
boc/packages/infrastructure/src/adapters/in-memory-unit-of-work.ts
T
Bernt 02b51d7814 PR-002: Persistence Adapters — in-memory, zero external dependencies
- Repository interfaces defined by domain (@landvex/domain)
- 6 in-memory adapters: Session, Mission, DecisionCase, Artifact, EventStore, UnitOfWork
- 9 tests verifying adapter contracts
- Domain unchanged — infrastructure depends on domain, never reverse
- ADR-006: In-Memory Adapters for Testing

Definition of Done met:
- All adapters compile against domain interfaces
- Unit tests pass (9/9)
- No PostgreSQL, S3, Express, AI in this PR
- Ready for PR-003: PostgreSQL adapters
2026-07-02 14:59:37 +00:00

99 lines
3.4 KiB
TypeScript

/**
* In-Memory Unit of Work
*
* Coordinates multiple in-memory repositories.
* Simulates transaction boundaries for testing.
*
* ADR-006: In-memory adapters for testing
* - commit() persists all changes
* - rollback() discards all changes
* - NOT atomic in production sense, but sufficient for tests
*/
import {
UnitOfWork,
FieldSessionRepository,
MissionRepository,
DecisionCaseRepository,
ArtifactRegistry,
EventStore,
} from '../repositories/repository-interfaces';
import { InMemoryFieldSessionRepository } from './in-memory-session-repository';
import { InMemoryMissionRepository } from './in-memory-mission-repository';
import { InMemoryDecisionCaseRepository } from './in-memory-decision-case-repository';
import { InMemoryArtifactRegistry } from './in-memory-artifact-registry';
import { InMemoryEventStore } from './in-memory-event-store';
export class InMemoryUnitOfWork implements UnitOfWork {
sessions: FieldSessionRepository;
missions: MissionRepository;
decisionCases: DecisionCaseRepository;
artifacts: ArtifactRegistry;
events: EventStore;
private snapshots: {
sessions: Map<string, unknown>;
missions: Map<string, unknown>;
cases: Map<string, unknown>;
artifacts: Map<string, unknown>;
events: unknown[];
} | null = null;
constructor() {
this.sessions = new InMemoryFieldSessionRepository();
this.missions = new InMemoryMissionRepository();
this.decisionCases = new InMemoryDecisionCaseRepository();
this.artifacts = new InMemoryArtifactRegistry();
this.events = new InMemoryEventStore();
}
async commit(): Promise<void> {
// In-memory: changes are already applied
// Real implementation would flush to database
this.snapshots = null;
}
async rollback(): Promise<void> {
if (!this.snapshots) {
throw new Error('No transaction in progress');
}
// Restore from snapshots
(this.sessions as InMemoryFieldSessionRepository).clear();
(this.missions as InMemoryMissionRepository).clear();
(this.decisionCases as InMemoryDecisionCaseRepository).clear();
(this.artifacts as InMemoryArtifactRegistry).clear();
(this.events as InMemoryEventStore).clear();
// Re-populate from snapshots
for (const [id, session] of this.snapshots.sessions) {
(this.sessions as InMemoryFieldSessionRepository).save(session as any);
}
for (const [id, mission] of this.snapshots.missions) {
(this.missions as InMemoryMissionRepository).save(mission as any);
}
for (const [id, c] of this.snapshots.cases) {
(this.decisionCases as InMemoryDecisionCaseRepository).save(c as any);
}
for (const [id, artifact] of this.snapshots.artifacts) {
(this.artifacts as InMemoryArtifactRegistry).register(artifact as any);
}
for (const event of this.snapshots.events) {
(this.events as InMemoryEventStore).append(event as any);
}
this.snapshots = null;
}
/** Begin transaction: capture snapshot */
begin(): void {
this.snapshots = {
sessions: new Map((this.sessions as InMemoryFieldSessionRepository as any).sessions),
missions: new Map((this.missions as InMemoryMissionRepository as any).missions),
cases: new Map((this.decisionCases as InMemoryDecisionCaseRepository as any).cases),
artifacts: new Map((this.artifacts as InMemoryArtifactRegistry as any).artifacts),
events: [...(this.events as InMemoryEventStore as any).events],
};
}
}