852 lines
24 KiB
Dart
852 lines
24 KiB
Dart
/// In-memory implementations of the scheduler persistence repository contracts.
|
|
///
|
|
/// These repositories are intended for core tests, local integration tests, and
|
|
/// adapter conformance checks. They model optimistic compare-and-set behavior
|
|
/// but do not provide durable storage.
|
|
library;
|
|
|
|
import 'dart:convert';
|
|
|
|
import 'package:scheduler_core/scheduler_core.dart' as core;
|
|
import 'package:scheduler_persistence/persistence.dart';
|
|
|
|
const _snapshotRetention = Duration(days: 30);
|
|
|
|
final _epoch = DateTime.fromMillisecondsSinceEpoch(0, isUtc: true);
|
|
|
|
/// In-memory task repository with owner scoping and revision checks.
|
|
final class InMemoryTaskRepository implements TaskRepository {
|
|
final Map<String, _Record<core.Task>> _records =
|
|
<String, _Record<core.Task>>{};
|
|
|
|
@override
|
|
Future<RepoResult<core.Task?>> findById({
|
|
required core.OwnerId ownerId,
|
|
required String taskId,
|
|
}) async {
|
|
final record = _records[taskId];
|
|
if (record == null || record.ownerId != ownerId) {
|
|
return const Right(null);
|
|
}
|
|
return Right(_cloneTask(record.value, record.ownerId, record.revision));
|
|
}
|
|
|
|
@override
|
|
Future<RepoResult<core.Page<core.Task>>> findByOwner({
|
|
required core.OwnerId ownerId,
|
|
core.PageRequest page = const core.PageRequest(),
|
|
}) async {
|
|
return Right(_pageRecords(
|
|
_records.values.where((record) => record.ownerId == ownerId),
|
|
page,
|
|
(record) => _cloneTask(record.value, record.ownerId, record.revision),
|
|
));
|
|
}
|
|
|
|
@override
|
|
Future<RepoResult<core.Page<core.Task>>> findByParentTaskId({
|
|
required core.OwnerId ownerId,
|
|
required String parentTaskId,
|
|
core.PageRequest page = const core.PageRequest(),
|
|
}) async {
|
|
return Right(_pageRecords(
|
|
_records.values.where(
|
|
(record) =>
|
|
record.ownerId == ownerId &&
|
|
record.value.parentTaskId == parentTaskId,
|
|
),
|
|
page,
|
|
(record) => _cloneTask(record.value, record.ownerId, record.revision),
|
|
));
|
|
}
|
|
|
|
@override
|
|
Future<RepoResult<core.Page<core.Task>>> findByProjectId({
|
|
required core.OwnerId ownerId,
|
|
required String projectId,
|
|
core.PageRequest page = const core.PageRequest(),
|
|
}) async {
|
|
return Right(_pageRecords(
|
|
_records.values.where(
|
|
(record) =>
|
|
record.ownerId == ownerId && record.value.projectId == projectId,
|
|
),
|
|
page,
|
|
(record) => _cloneTask(record.value, record.ownerId, record.revision),
|
|
));
|
|
}
|
|
|
|
@override
|
|
Future<RepoResult<core.Page<core.Task>>> findByStatus({
|
|
required core.OwnerId ownerId,
|
|
required core.TaskStatus status,
|
|
core.PageRequest page = const core.PageRequest(),
|
|
}) async {
|
|
return Right(_pageRecords(
|
|
_records.values.where(
|
|
(record) => record.ownerId == ownerId && record.value.status == status,
|
|
),
|
|
page,
|
|
(record) => _cloneTask(record.value, record.ownerId, record.revision),
|
|
));
|
|
}
|
|
|
|
@override
|
|
Future<RepoResult<core.Revision>> insert({
|
|
required core.OwnerId ownerId,
|
|
required core.Task task,
|
|
}) async {
|
|
if (_records.containsKey(task.id)) {
|
|
return Left(RepositoryDuplicateId(entityId: task.id));
|
|
}
|
|
_records[task.id] = _Record(
|
|
id: task.id,
|
|
ownerId: ownerId,
|
|
value: _cloneTask(task, ownerId, core.Revision.initial),
|
|
revision: core.Revision.initial,
|
|
);
|
|
return const Right(core.Revision.initial);
|
|
}
|
|
|
|
@override
|
|
Future<RepoResult<core.Revision>> save({
|
|
required core.OwnerId ownerId,
|
|
required core.Task task,
|
|
required core.Revision expectedRevision,
|
|
}) async {
|
|
final checked = _checkWrite(
|
|
records: _records,
|
|
id: task.id,
|
|
ownerId: ownerId,
|
|
expectedRevision: expectedRevision,
|
|
);
|
|
if (checked is Left<RepositoryFailure, _Record<core.Task>>) {
|
|
return Left(checked.value);
|
|
}
|
|
final record = checked.right;
|
|
final nextRevision = record.revision.next();
|
|
_records[task.id] = record.copyWith(
|
|
value: _cloneTask(task, ownerId, nextRevision),
|
|
revision: nextRevision,
|
|
updatedAt: task.updatedAt,
|
|
);
|
|
return Right(nextRevision);
|
|
}
|
|
|
|
@override
|
|
Future<RepoResult<core.Revision>> delete({
|
|
required core.OwnerId ownerId,
|
|
required String taskId,
|
|
required core.Revision expectedRevision,
|
|
}) async {
|
|
final checked = _checkWrite(
|
|
records: _records,
|
|
id: taskId,
|
|
ownerId: ownerId,
|
|
expectedRevision: expectedRevision,
|
|
);
|
|
if (checked is Left<RepositoryFailure, _Record<core.Task>>) {
|
|
return Left(checked.value);
|
|
}
|
|
final nextRevision = checked.right.revision.next();
|
|
_records.remove(taskId);
|
|
return Right(nextRevision);
|
|
}
|
|
}
|
|
|
|
/// In-memory project repository with owner scoping and revision checks.
|
|
final class InMemoryProjectRepository implements ProjectRepository {
|
|
final Map<String, _Record<core.ProjectProfile>> _records =
|
|
<String, _Record<core.ProjectProfile>>{};
|
|
|
|
@override
|
|
Future<RepoResult<core.ProjectProfile?>> findById({
|
|
required core.OwnerId ownerId,
|
|
required String projectId,
|
|
}) async {
|
|
final record = _records[projectId];
|
|
if (record == null || record.ownerId != ownerId) {
|
|
return const Right(null);
|
|
}
|
|
return Right(_cloneProject(record));
|
|
}
|
|
|
|
@override
|
|
Future<RepoResult<core.Page<core.ProjectProfile>>> findByOwner({
|
|
required core.OwnerId ownerId,
|
|
bool includeArchived = false,
|
|
core.PageRequest page = const core.PageRequest(),
|
|
}) async {
|
|
return Right(_pageRecords(
|
|
_records.values.where(
|
|
(record) =>
|
|
record.ownerId == ownerId &&
|
|
(includeArchived || !record.value.isArchived),
|
|
),
|
|
page,
|
|
_cloneProject,
|
|
));
|
|
}
|
|
|
|
@override
|
|
Future<RepoResult<core.Revision>> insert({
|
|
required core.OwnerId ownerId,
|
|
required core.ProjectProfile project,
|
|
}) async {
|
|
if (_records.containsKey(project.id)) {
|
|
return Left(RepositoryDuplicateId(entityId: project.id));
|
|
}
|
|
_records[project.id] = _Record(
|
|
id: project.id,
|
|
ownerId: ownerId,
|
|
value: _cloneProjectValue(
|
|
project,
|
|
ownerId: ownerId,
|
|
revision: core.Revision.initial,
|
|
createdAt: _epoch,
|
|
updatedAt: _epoch,
|
|
),
|
|
revision: core.Revision.initial,
|
|
createdAt: _epoch,
|
|
updatedAt: _epoch,
|
|
);
|
|
return const Right(core.Revision.initial);
|
|
}
|
|
|
|
@override
|
|
Future<RepoResult<core.Revision>> save({
|
|
required core.OwnerId ownerId,
|
|
required core.ProjectProfile project,
|
|
required core.Revision expectedRevision,
|
|
}) async {
|
|
final checked = _checkWrite(
|
|
records: _records,
|
|
id: project.id,
|
|
ownerId: ownerId,
|
|
expectedRevision: expectedRevision,
|
|
);
|
|
if (checked is Left<RepositoryFailure, _Record<core.ProjectProfile>>) {
|
|
return Left(checked.value);
|
|
}
|
|
final record = checked.right;
|
|
final nextRevision = record.revision.next();
|
|
_records[project.id] = record.copyWith(
|
|
value: _cloneProjectValue(
|
|
project,
|
|
ownerId: ownerId,
|
|
revision: nextRevision,
|
|
createdAt: record.createdAt,
|
|
updatedAt: _epoch,
|
|
),
|
|
revision: nextRevision,
|
|
updatedAt: _epoch,
|
|
);
|
|
return Right(nextRevision);
|
|
}
|
|
|
|
@override
|
|
Future<RepoResult<core.Revision>> archive({
|
|
required core.OwnerId ownerId,
|
|
required String projectId,
|
|
required core.Revision expectedRevision,
|
|
required DateTime archivedAtUtc,
|
|
}) async {
|
|
final checked = _checkWrite(
|
|
records: _records,
|
|
id: projectId,
|
|
ownerId: ownerId,
|
|
expectedRevision: expectedRevision,
|
|
);
|
|
if (checked is Left<RepositoryFailure, _Record<core.ProjectProfile>>) {
|
|
return Left(checked.value);
|
|
}
|
|
final record = checked.right;
|
|
final nextRevision = record.revision.next();
|
|
final archived = record.value.copyWith(archivedAt: archivedAtUtc);
|
|
_records[projectId] = record.copyWith(
|
|
value: _cloneProjectValue(
|
|
archived,
|
|
ownerId: ownerId,
|
|
revision: nextRevision,
|
|
createdAt: record.createdAt,
|
|
updatedAt: _epoch,
|
|
),
|
|
revision: nextRevision,
|
|
updatedAt: _epoch,
|
|
);
|
|
return Right(nextRevision);
|
|
}
|
|
}
|
|
|
|
/// In-memory locked-block repository with owner scoping and revision checks.
|
|
final class InMemoryLockedBlockRepository implements LockedBlockRepository {
|
|
final Map<String, _Record<core.LockedBlock>> _blocks =
|
|
<String, _Record<core.LockedBlock>>{};
|
|
final Map<String, _Record<core.LockedBlockOverride>> _overrides =
|
|
<String, _Record<core.LockedBlockOverride>>{};
|
|
|
|
@override
|
|
Future<RepoResult<core.LockedBlock?>> findBlockById({
|
|
required core.OwnerId ownerId,
|
|
required String blockId,
|
|
}) async {
|
|
final record = _blocks[blockId];
|
|
if (record == null || record.ownerId != ownerId) {
|
|
return const Right(null);
|
|
}
|
|
return Right(
|
|
_cloneLockedBlock(record.value, record.ownerId, record.revision));
|
|
}
|
|
|
|
@override
|
|
Future<RepoResult<core.Page<core.LockedBlock>>> findBlocksByOwner({
|
|
required core.OwnerId ownerId,
|
|
bool includeArchived = false,
|
|
core.PageRequest page = const core.PageRequest(),
|
|
}) async {
|
|
return Right(_pageRecords(
|
|
_blocks.values.where(
|
|
(record) =>
|
|
record.ownerId == ownerId &&
|
|
(includeArchived || !record.value.isArchived),
|
|
),
|
|
page,
|
|
(record) => _cloneLockedBlock(
|
|
record.value,
|
|
record.ownerId,
|
|
record.revision,
|
|
),
|
|
));
|
|
}
|
|
|
|
@override
|
|
Future<RepoResult<core.LockedBlockOverride?>> findOverrideById({
|
|
required core.OwnerId ownerId,
|
|
required String overrideId,
|
|
}) async {
|
|
final record = _overrides[overrideId];
|
|
if (record == null || record.ownerId != ownerId) {
|
|
return const Right(null);
|
|
}
|
|
return Right(
|
|
_cloneLockedBlockOverride(record.value, record.ownerId, record.revision),
|
|
);
|
|
}
|
|
|
|
@override
|
|
Future<RepoResult<core.Page<core.LockedBlockOverride>>> findOverridesForDate({
|
|
required core.OwnerId ownerId,
|
|
required core.CivilDate date,
|
|
core.PageRequest page = const core.PageRequest(),
|
|
}) async {
|
|
return Right(_pageRecords(
|
|
_overrides.values.where(
|
|
(record) => record.ownerId == ownerId && record.value.date == date,
|
|
),
|
|
page,
|
|
(record) => _cloneLockedBlockOverride(
|
|
record.value,
|
|
record.ownerId,
|
|
record.revision,
|
|
),
|
|
));
|
|
}
|
|
|
|
@override
|
|
Future<RepoResult<core.Revision>> insertBlock({
|
|
required core.OwnerId ownerId,
|
|
required core.LockedBlock block,
|
|
}) async {
|
|
if (_blocks.containsKey(block.id)) {
|
|
return Left(RepositoryDuplicateId(entityId: block.id));
|
|
}
|
|
_blocks[block.id] = _Record(
|
|
id: block.id,
|
|
ownerId: ownerId,
|
|
value: _cloneLockedBlock(block, ownerId, core.Revision.initial),
|
|
revision: core.Revision.initial,
|
|
);
|
|
return const Right(core.Revision.initial);
|
|
}
|
|
|
|
@override
|
|
Future<RepoResult<core.Revision>> saveBlock({
|
|
required core.OwnerId ownerId,
|
|
required core.LockedBlock block,
|
|
required core.Revision expectedRevision,
|
|
}) async {
|
|
final checked = _checkWrite(
|
|
records: _blocks,
|
|
id: block.id,
|
|
ownerId: ownerId,
|
|
expectedRevision: expectedRevision,
|
|
);
|
|
if (checked is Left<RepositoryFailure, _Record<core.LockedBlock>>) {
|
|
return Left(checked.value);
|
|
}
|
|
final record = checked.right;
|
|
final nextRevision = record.revision.next();
|
|
_blocks[block.id] = record.copyWith(
|
|
value: _cloneLockedBlock(block, ownerId, nextRevision),
|
|
revision: nextRevision,
|
|
updatedAt: block.updatedAt,
|
|
);
|
|
return Right(nextRevision);
|
|
}
|
|
|
|
@override
|
|
Future<RepoResult<core.Revision>> archiveBlock({
|
|
required core.OwnerId ownerId,
|
|
required String blockId,
|
|
required core.Revision expectedRevision,
|
|
required DateTime archivedAtUtc,
|
|
}) async {
|
|
final checked = _checkWrite(
|
|
records: _blocks,
|
|
id: blockId,
|
|
ownerId: ownerId,
|
|
expectedRevision: expectedRevision,
|
|
);
|
|
if (checked is Left<RepositoryFailure, _Record<core.LockedBlock>>) {
|
|
return Left(checked.value);
|
|
}
|
|
final record = checked.right;
|
|
final nextRevision = record.revision.next();
|
|
final archived = record.value.copyWith(archivedAt: archivedAtUtc);
|
|
_blocks[blockId] = record.copyWith(
|
|
value: _cloneLockedBlock(archived, ownerId, nextRevision),
|
|
revision: nextRevision,
|
|
updatedAt: archived.updatedAt,
|
|
);
|
|
return Right(nextRevision);
|
|
}
|
|
|
|
@override
|
|
Future<RepoResult<core.Revision>> insertOverride({
|
|
required core.OwnerId ownerId,
|
|
required core.LockedBlockOverride override,
|
|
}) async {
|
|
if (_overrides.containsKey(override.id)) {
|
|
return Left(RepositoryDuplicateId(entityId: override.id));
|
|
}
|
|
_overrides[override.id] = _Record(
|
|
id: override.id,
|
|
ownerId: ownerId,
|
|
value: _cloneLockedBlockOverride(
|
|
override,
|
|
ownerId,
|
|
core.Revision.initial,
|
|
),
|
|
revision: core.Revision.initial,
|
|
);
|
|
return const Right(core.Revision.initial);
|
|
}
|
|
|
|
@override
|
|
Future<RepoResult<core.Revision>> saveOverride({
|
|
required core.OwnerId ownerId,
|
|
required core.LockedBlockOverride override,
|
|
required core.Revision expectedRevision,
|
|
}) async {
|
|
final checked = _checkWrite(
|
|
records: _overrides,
|
|
id: override.id,
|
|
ownerId: ownerId,
|
|
expectedRevision: expectedRevision,
|
|
);
|
|
if (checked is Left<RepositoryFailure, _Record<core.LockedBlockOverride>>) {
|
|
return Left(checked.value);
|
|
}
|
|
final record = checked.right;
|
|
final nextRevision = record.revision.next();
|
|
_overrides[override.id] = record.copyWith(
|
|
value: _cloneLockedBlockOverride(override, ownerId, nextRevision),
|
|
revision: nextRevision,
|
|
updatedAt: override.updatedAt,
|
|
);
|
|
return Right(nextRevision);
|
|
}
|
|
}
|
|
|
|
/// In-memory owner settings repository with owner scoping and revision checks.
|
|
final class InMemorySettingsRepository implements SettingsRepository {
|
|
final Map<String, _Record<core.OwnerSettings>> _records =
|
|
<String, _Record<core.OwnerSettings>>{};
|
|
|
|
@override
|
|
Future<RepoResult<core.OwnerSettings?>> findByOwner({
|
|
required core.OwnerId ownerId,
|
|
}) async {
|
|
final record = _records[ownerId.value];
|
|
if (record == null || record.ownerId != ownerId) {
|
|
return const Right(null);
|
|
}
|
|
return Right(_cloneSettings(record));
|
|
}
|
|
|
|
@override
|
|
Future<RepoResult<core.Revision>> insert({
|
|
required core.OwnerId ownerId,
|
|
required core.OwnerSettings settings,
|
|
}) async {
|
|
if (_records.containsKey(ownerId.value)) {
|
|
return Left(RepositoryDuplicateId(entityId: ownerId.value));
|
|
}
|
|
final ownedSettings = settings.ownerId == ownerId.value
|
|
? settings
|
|
: settings.copyWith(ownerId: ownerId.value);
|
|
_records[ownerId.value] = _Record(
|
|
id: ownerId.value,
|
|
ownerId: ownerId,
|
|
value: _cloneSettingsValue(
|
|
ownedSettings,
|
|
revision: core.Revision.initial,
|
|
createdAt: _epoch,
|
|
updatedAt: _epoch,
|
|
),
|
|
revision: core.Revision.initial,
|
|
createdAt: _epoch,
|
|
updatedAt: _epoch,
|
|
);
|
|
return const Right(core.Revision.initial);
|
|
}
|
|
|
|
@override
|
|
Future<RepoResult<core.Revision>> save({
|
|
required core.OwnerId ownerId,
|
|
required core.OwnerSettings settings,
|
|
required core.Revision expectedRevision,
|
|
}) async {
|
|
final checked = _checkWrite(
|
|
records: _records,
|
|
id: ownerId.value,
|
|
ownerId: ownerId,
|
|
expectedRevision: expectedRevision,
|
|
);
|
|
if (checked is Left<RepositoryFailure, _Record<core.OwnerSettings>>) {
|
|
return Left(checked.value);
|
|
}
|
|
final record = checked.right;
|
|
final nextRevision = record.revision.next();
|
|
final ownedSettings = settings.ownerId == ownerId.value
|
|
? settings
|
|
: settings.copyWith(ownerId: ownerId.value);
|
|
_records[ownerId.value] = record.copyWith(
|
|
value: _cloneSettingsValue(
|
|
ownedSettings,
|
|
revision: nextRevision,
|
|
createdAt: record.createdAt,
|
|
updatedAt: _epoch,
|
|
),
|
|
revision: nextRevision,
|
|
updatedAt: _epoch,
|
|
);
|
|
return Right(nextRevision);
|
|
}
|
|
}
|
|
|
|
/// In-memory diagnostic schedule snapshot repository.
|
|
final class InMemoryScheduleSnapshotRepository
|
|
implements ScheduleSnapshotRepository {
|
|
final Map<String, _SnapshotRecord> _records = <String, _SnapshotRecord>{};
|
|
|
|
@override
|
|
Future<RepoResult<core.SchedulingStateSnapshot?>> findById({
|
|
required core.OwnerId ownerId,
|
|
required String snapshotId,
|
|
}) async {
|
|
final record = _records[snapshotId];
|
|
if (record == null || record.ownerId != ownerId) {
|
|
return const Right(null);
|
|
}
|
|
return Right(_cloneSnapshot(record.value, record.ownerId, record.revision));
|
|
}
|
|
|
|
@override
|
|
Future<RepoResult<core.Page<core.SchedulingStateSnapshot>>> findInWindow({
|
|
required core.OwnerId ownerId,
|
|
required core.SchedulingWindow window,
|
|
core.PageRequest page = const core.PageRequest(),
|
|
}) async {
|
|
return Right(_pageRecords(
|
|
_records.values.where(
|
|
(record) =>
|
|
record.ownerId == ownerId &&
|
|
_windowsOverlap(record.value.window, window),
|
|
),
|
|
page,
|
|
(record) => _cloneSnapshot(record.value, record.ownerId, record.revision),
|
|
));
|
|
}
|
|
|
|
@override
|
|
Future<RepoResult<core.Revision>> insert({
|
|
required core.OwnerId ownerId,
|
|
required core.SchedulingStateSnapshot snapshot,
|
|
}) async {
|
|
if (_records.containsKey(snapshot.id)) {
|
|
return Left(RepositoryDuplicateId(entityId: snapshot.id));
|
|
}
|
|
_records[snapshot.id] = _SnapshotRecord(
|
|
id: snapshot.id,
|
|
ownerId: ownerId,
|
|
value: _cloneSnapshot(snapshot, ownerId, core.Revision.initial),
|
|
revision: core.Revision.initial,
|
|
retentionExpiresAt: snapshot.capturedAt.add(_snapshotRetention),
|
|
);
|
|
return const Right(core.Revision.initial);
|
|
}
|
|
|
|
@override
|
|
Future<RepoResult<int>> deleteExpired({
|
|
required core.OwnerId ownerId,
|
|
required DateTime nowUtc,
|
|
}) async {
|
|
final expiredIds = _records.values
|
|
.where(
|
|
(record) =>
|
|
record.ownerId == ownerId &&
|
|
record.retentionExpiresAt.isBefore(nowUtc),
|
|
)
|
|
.map((record) => record.id)
|
|
.toList(growable: false);
|
|
for (final id in expiredIds) {
|
|
_records.remove(id);
|
|
}
|
|
return Right(expiredIds.length);
|
|
}
|
|
}
|
|
|
|
Either<RepositoryFailure, _Record<T>> _checkWrite<T>({
|
|
required Map<String, _Record<T>> records,
|
|
required String id,
|
|
required core.OwnerId ownerId,
|
|
required core.Revision expectedRevision,
|
|
}) {
|
|
if (expectedRevision.value <= 0) {
|
|
return Left(
|
|
RepositoryInvalidRevision(
|
|
entityId: id,
|
|
expectedRevision: expectedRevision,
|
|
),
|
|
);
|
|
}
|
|
final record = records[id];
|
|
if (record == null) {
|
|
return Left(RepositoryNotFound(entityId: id));
|
|
}
|
|
if (record.ownerId != ownerId) {
|
|
return Left(RepositoryOwnerMismatch(entityId: id));
|
|
}
|
|
if (record.revision != expectedRevision) {
|
|
return Left(
|
|
RepositoryStaleRevision(
|
|
entityId: id,
|
|
expectedRevision: expectedRevision,
|
|
actualRevision: record.revision,
|
|
),
|
|
);
|
|
}
|
|
return Right(record);
|
|
}
|
|
|
|
core.Page<R> _pageRecords<T extends _IdentifiedRecord, R>(
|
|
Iterable<T> records,
|
|
core.PageRequest page,
|
|
R Function(T record) clone,
|
|
) {
|
|
final ordered = records.toList(growable: false)
|
|
..sort((left, right) => left.id.compareTo(right.id));
|
|
final start = _cursorOffset(page.cursor);
|
|
final end = (start + page.limit).clamp(0, ordered.length);
|
|
final items = ordered.sublist(start, end).map(clone).toList(growable: false);
|
|
final nextCursor = end < ordered.length ? end.toString() : null;
|
|
return core.Page<R>(items: items, nextCursor: nextCursor);
|
|
}
|
|
|
|
int _cursorOffset(String? cursor) {
|
|
if (cursor == null) {
|
|
return 0;
|
|
}
|
|
final parsed = int.tryParse(cursor);
|
|
if (parsed == null || parsed < 0) {
|
|
return 0;
|
|
}
|
|
return parsed;
|
|
}
|
|
|
|
bool _windowsOverlap(core.SchedulingWindow left, core.SchedulingWindow right) {
|
|
return left.start.isBefore(right.end) && right.start.isBefore(left.end);
|
|
}
|
|
|
|
core.Task _cloneTask(
|
|
core.Task task,
|
|
core.OwnerId ownerId,
|
|
core.Revision revision,
|
|
) {
|
|
return core.TaskDocumentMapping.fromDocument(_roundTrip(
|
|
core.TaskDocumentMapping.toDocument(
|
|
task,
|
|
ownerId: ownerId.value,
|
|
revision: revision.value,
|
|
),
|
|
));
|
|
}
|
|
|
|
core.ProjectProfile _cloneProject(_Record<core.ProjectProfile> record) {
|
|
return _cloneProjectValue(
|
|
record.value,
|
|
ownerId: record.ownerId,
|
|
revision: record.revision,
|
|
createdAt: record.createdAt,
|
|
updatedAt: record.updatedAt,
|
|
);
|
|
}
|
|
|
|
core.ProjectProfile _cloneProjectValue(
|
|
core.ProjectProfile project, {
|
|
required core.OwnerId ownerId,
|
|
required core.Revision revision,
|
|
required DateTime createdAt,
|
|
required DateTime updatedAt,
|
|
}) {
|
|
return core.ProjectDocumentMapping.fromDocument(_roundTrip(
|
|
core.ProjectDocumentMapping.toDocument(
|
|
project,
|
|
ownerId: ownerId.value,
|
|
revision: revision.value,
|
|
createdAt: createdAt,
|
|
updatedAt: updatedAt,
|
|
),
|
|
));
|
|
}
|
|
|
|
core.LockedBlock _cloneLockedBlock(
|
|
core.LockedBlock block,
|
|
core.OwnerId ownerId,
|
|
core.Revision revision,
|
|
) {
|
|
return core.LockedBlockDocumentMapping.fromDocument(_roundTrip(
|
|
core.LockedBlockDocumentMapping.toDocument(
|
|
block,
|
|
ownerId: ownerId.value,
|
|
revision: revision.value,
|
|
),
|
|
));
|
|
}
|
|
|
|
core.LockedBlockOverride _cloneLockedBlockOverride(
|
|
core.LockedBlockOverride override,
|
|
core.OwnerId ownerId,
|
|
core.Revision revision,
|
|
) {
|
|
return core.LockedBlockOverrideDocumentMapping.fromDocument(_roundTrip(
|
|
core.LockedBlockOverrideDocumentMapping.toDocument(
|
|
override,
|
|
ownerId: ownerId.value,
|
|
revision: revision.value,
|
|
),
|
|
));
|
|
}
|
|
|
|
core.OwnerSettings _cloneSettings(_Record<core.OwnerSettings> record) {
|
|
return _cloneSettingsValue(
|
|
record.value,
|
|
revision: record.revision,
|
|
createdAt: record.createdAt,
|
|
updatedAt: record.updatedAt,
|
|
);
|
|
}
|
|
|
|
core.OwnerSettings _cloneSettingsValue(
|
|
core.OwnerSettings settings, {
|
|
required core.Revision revision,
|
|
required DateTime createdAt,
|
|
required DateTime updatedAt,
|
|
}) {
|
|
return core.OwnerSettingsDocumentMapping.fromDocument(_roundTrip(
|
|
core.OwnerSettingsDocumentMapping.toDocument(
|
|
settings,
|
|
revision: revision.value,
|
|
createdAt: createdAt,
|
|
updatedAt: updatedAt,
|
|
),
|
|
));
|
|
}
|
|
|
|
core.SchedulingStateSnapshot _cloneSnapshot(
|
|
core.SchedulingStateSnapshot snapshot,
|
|
core.OwnerId ownerId,
|
|
core.Revision revision,
|
|
) {
|
|
return core.SchedulingSnapshotDocumentMapping.fromDocument(_roundTrip(
|
|
core.SchedulingSnapshotDocumentMapping.toDocument(
|
|
snapshot,
|
|
ownerId: ownerId.value,
|
|
revision: revision.value,
|
|
retentionExpiresAt: snapshot.capturedAt.add(_snapshotRetention),
|
|
),
|
|
));
|
|
}
|
|
|
|
Map<String, Object?> _roundTrip(Map<String, Object?> document) {
|
|
return Map<String, Object?>.from(
|
|
jsonDecode(jsonEncode(document)) as Map<dynamic, dynamic>,
|
|
);
|
|
}
|
|
|
|
abstract interface class _IdentifiedRecord {
|
|
String get id;
|
|
}
|
|
|
|
final class _Record<T> implements _IdentifiedRecord {
|
|
_Record({
|
|
required this.id,
|
|
required this.ownerId,
|
|
required this.value,
|
|
required this.revision,
|
|
DateTime? createdAt,
|
|
DateTime? updatedAt,
|
|
}) : createdAt = createdAt ?? _epoch,
|
|
updatedAt = updatedAt ?? _epoch;
|
|
|
|
@override
|
|
final String id;
|
|
final core.OwnerId ownerId;
|
|
final T value;
|
|
final core.Revision revision;
|
|
final DateTime createdAt;
|
|
final DateTime updatedAt;
|
|
|
|
_Record<T> copyWith({
|
|
T? value,
|
|
core.Revision? revision,
|
|
DateTime? updatedAt,
|
|
}) {
|
|
return _Record<T>(
|
|
id: id,
|
|
ownerId: ownerId,
|
|
value: value ?? this.value,
|
|
revision: revision ?? this.revision,
|
|
createdAt: createdAt,
|
|
updatedAt: updatedAt ?? this.updatedAt,
|
|
);
|
|
}
|
|
}
|
|
|
|
final class _SnapshotRecord implements _IdentifiedRecord {
|
|
_SnapshotRecord({
|
|
required this.id,
|
|
required this.ownerId,
|
|
required this.value,
|
|
required this.revision,
|
|
required this.retentionExpiresAt,
|
|
});
|
|
|
|
@override
|
|
final String id;
|
|
final core.OwnerId ownerId;
|
|
final core.SchedulingStateSnapshot value;
|
|
final core.Revision revision;
|
|
final DateTime retentionExpiresAt;
|
|
}
|