focus-flow/lib/src/repositories.dart

300 lines
9 KiB
Dart

// Persistence boundary interfaces for the scheduling core.
//
// These interfaces are intentionally database-client-free. A future MongoDB
// adapter can implement them with document storage while the domain layer and
// tests keep using the same pure Dart contracts.
import 'locked_time.dart';
import 'models.dart';
import 'scheduling_engine.dart';
/// Repository contract for task documents.
abstract interface class TaskRepository {
/// Return a task by stable id, or null when it does not exist.
Future<Task?> findById(String id);
/// Return all tasks currently known to the repository.
Future<List<Task>> findAll();
/// Return tasks with a lifecycle status.
Future<List<Task>> findByStatus(TaskStatus status);
/// Return tasks that overlap [window] by scheduled start/end.
Future<List<Task>> findScheduledInWindow(SchedulingWindow window);
/// Save or replace [task] by stable id.
Future<void> save(Task task);
/// Save or replace every task by stable id.
Future<void> saveAll(Iterable<Task> tasks);
}
/// Repository contract for project profile documents.
abstract interface class ProjectRepository {
/// Return a project by stable id, or null when it does not exist.
Future<ProjectProfile?> findById(String id);
/// Return all projects currently known to the repository.
Future<List<ProjectProfile>> findAll();
/// Save or replace [project] by stable id.
Future<void> save(ProjectProfile project);
}
/// Repository contract for locked-block and one-day override documents.
abstract interface class LockedBlockRepository {
/// Return a locked block by stable id, or null when it does not exist.
Future<LockedBlock?> findBlockById(String id);
/// Return all locked block definitions.
Future<List<LockedBlock>> findAllBlocks();
/// Return an override by stable id, or null when it does not exist.
Future<LockedBlockOverride?> findOverrideById(String id);
/// Return all one-day overrides.
Future<List<LockedBlockOverride>> findAllOverrides();
/// Save or replace [block] by stable id.
Future<void> saveBlock(LockedBlock block);
/// Save or replace [override] by stable id.
Future<void> saveOverride(LockedBlockOverride override);
}
/// Snapshot of one scheduling operation or persisted planning state.
class SchedulingStateSnapshot {
const SchedulingStateSnapshot({
required this.id,
required this.capturedAt,
required this.window,
required this.tasks,
this.lockedIntervals = const <TimeInterval>[],
this.requiredVisibleIntervals = const <TimeInterval>[],
this.notices = const <SchedulingNotice>[],
this.changes = const <SchedulingChange>[],
this.overlaps = const <SchedulingOverlap>[],
this.operationName,
});
/// Stable snapshot id.
final String id;
/// Time this snapshot was created.
final DateTime capturedAt;
/// Planning window represented by the snapshot.
final SchedulingWindow window;
/// Task documents visible to the operation.
final List<Task> tasks;
/// Locked intervals supplied to the scheduler.
final List<TimeInterval> lockedIntervals;
/// Extra visible required intervals supplied to the scheduler.
final List<TimeInterval> requiredVisibleIntervals;
/// Human-readable scheduling notices.
final List<SchedulingNotice> notices;
/// Machine-readable task movements.
final List<SchedulingChange> changes;
/// Analysis-only overlap findings.
final List<SchedulingOverlap> overlaps;
/// Optional operation label, such as `push` or `rollover`.
final String? operationName;
/// Rebuild scheduler input from the persisted state shape.
SchedulingInput get input {
return SchedulingInput(
tasks: List<Task>.unmodifiable(tasks),
window: window,
lockedIntervals: List<TimeInterval>.unmodifiable(lockedIntervals),
requiredVisibleIntervals:
List<TimeInterval>.unmodifiable(requiredVisibleIntervals),
);
}
/// Rebuild scheduler result details from the persisted state shape.
SchedulingResult get result {
return SchedulingResult(
tasks: List<Task>.unmodifiable(tasks),
notices: List<SchedulingNotice>.unmodifiable(notices),
changes: List<SchedulingChange>.unmodifiable(changes),
overlaps: List<SchedulingOverlap>.unmodifiable(overlaps),
);
}
}
/// Repository contract for scheduling operation/state snapshot documents.
abstract interface class SchedulingSnapshotRepository {
/// Return a snapshot by stable id, or null when it does not exist.
Future<SchedulingStateSnapshot?> findById(String id);
/// Return all snapshots overlapping [window].
Future<List<SchedulingStateSnapshot>> findInWindow(SchedulingWindow window);
/// Save or replace [snapshot] by stable id.
Future<void> save(SchedulingStateSnapshot snapshot);
}
/// In-memory task repository useful for tests and early application wiring.
class InMemoryTaskRepository implements TaskRepository {
InMemoryTaskRepository([Iterable<Task> initialTasks = const []])
: _tasksById = {
for (final task in initialTasks) task.id: task,
};
final Map<String, Task> _tasksById;
@override
Future<Task?> findById(String id) async {
return _tasksById[id];
}
@override
Future<List<Task>> findAll() async {
return List<Task>.unmodifiable(_tasksById.values);
}
@override
Future<List<Task>> findByStatus(TaskStatus status) async {
return List<Task>.unmodifiable(
_tasksById.values.where((task) => task.status == status),
);
}
@override
Future<List<Task>> findScheduledInWindow(SchedulingWindow window) async {
return List<Task>.unmodifiable(
_tasksById.values.where((task) {
final start = task.scheduledStart;
final end = task.scheduledEnd;
if (start == null || end == null) {
return false;
}
return TimeInterval(start: start, end: end).overlaps(window.interval);
}),
);
}
@override
Future<void> save(Task task) async {
_tasksById[task.id] = task;
}
@override
Future<void> saveAll(Iterable<Task> tasks) async {
for (final task in tasks) {
_tasksById[task.id] = task;
}
}
}
/// In-memory project repository useful for tests and early application wiring.
class InMemoryProjectRepository implements ProjectRepository {
InMemoryProjectRepository(
[Iterable<ProjectProfile> initialProjects = const []])
: _projectsById = {
for (final project in initialProjects) project.id: project,
};
final Map<String, ProjectProfile> _projectsById;
@override
Future<ProjectProfile?> findById(String id) async {
return _projectsById[id];
}
@override
Future<List<ProjectProfile>> findAll() async {
return List<ProjectProfile>.unmodifiable(_projectsById.values);
}
@override
Future<void> save(ProjectProfile project) async {
_projectsById[project.id] = project;
}
}
/// In-memory locked-time repository useful for tests and early wiring.
class InMemoryLockedBlockRepository implements LockedBlockRepository {
InMemoryLockedBlockRepository({
Iterable<LockedBlock> initialBlocks = const [],
Iterable<LockedBlockOverride> initialOverrides = const [],
}) : _blocksById = {
for (final block in initialBlocks) block.id: block,
},
_overridesById = {
for (final override in initialOverrides) override.id: override,
};
final Map<String, LockedBlock> _blocksById;
final Map<String, LockedBlockOverride> _overridesById;
@override
Future<LockedBlock?> findBlockById(String id) async {
return _blocksById[id];
}
@override
Future<List<LockedBlock>> findAllBlocks() async {
return List<LockedBlock>.unmodifiable(_blocksById.values);
}
@override
Future<LockedBlockOverride?> findOverrideById(String id) async {
return _overridesById[id];
}
@override
Future<List<LockedBlockOverride>> findAllOverrides() async {
return List<LockedBlockOverride>.unmodifiable(_overridesById.values);
}
@override
Future<void> saveBlock(LockedBlock block) async {
_blocksById[block.id] = block;
}
@override
Future<void> saveOverride(LockedBlockOverride override) async {
_overridesById[override.id] = override;
}
}
/// In-memory snapshot repository useful for tests and early application wiring.
class InMemorySchedulingSnapshotRepository
implements SchedulingSnapshotRepository {
InMemorySchedulingSnapshotRepository([
Iterable<SchedulingStateSnapshot> initialSnapshots = const [],
]) : _snapshotsById = {
for (final snapshot in initialSnapshots) snapshot.id: snapshot,
};
final Map<String, SchedulingStateSnapshot> _snapshotsById;
@override
Future<SchedulingStateSnapshot?> findById(String id) async {
return _snapshotsById[id];
}
@override
Future<List<SchedulingStateSnapshot>> findInWindow(
SchedulingWindow window,
) async {
return List<SchedulingStateSnapshot>.unmodifiable(
_snapshotsById.values.where(
(snapshot) => snapshot.window.interval.overlaps(window.interval),
),
);
}
@override
Future<void> save(SchedulingStateSnapshot snapshot) async {
_snapshotsById[snapshot.id] = snapshot;
}
}