MigrationPlan.resolve constructor

MigrationPlan.resolve(
  1. List<Migration> migrations, {
  2. required int fromVersion,
  3. required int toVersion,
})

Builds the ordered chain from fromVersion to toVersion out of migrations.

Throws a MigrationException describing every problem found — a duplicate starting version (ambiguous path), a step that does not move forward, a gap in the chain, or a step that overshoots the target — so misconfiguration fails fast instead of mid-migration.

Implementation

factory MigrationPlan.resolve(
  List<Migration> migrations, {
  required int fromVersion,
  required int toVersion,
}) {
  if (fromVersion >= toVersion) return MigrationPlan._(const []);

  final problems = <String>[];
  final byFromVersion = <int, Migration>{};
  for (final migration in migrations) {
    if (migration.toVersion <= migration.fromVersion) {
      problems.add(
        'Migration v${migration.fromVersion} -> v${migration.toVersion} does not move forward.',
      );
      continue;
    }
    final existing = byFromVersion[migration.fromVersion];
    if (existing != null) {
      problems.add(
        'Two migrations start at v${migration.fromVersion} '
        '(to v${existing.toVersion} and to v${migration.toVersion}); the path is ambiguous.',
      );
      continue;
    }
    byFromVersion[migration.fromVersion] = migration;
  }

  final steps = <Migration>[];
  var currentVersion = fromVersion;
  while (problems.isEmpty && currentVersion < toVersion) {
    final next = byFromVersion[currentVersion];
    if (next == null) {
      problems.add(
        'Migration path broken: no migration starts at v$currentVersion '
        '(migrating v$fromVersion -> v$toVersion).',
      );
      break;
    }
    if (next.toVersion > toVersion) {
      problems.add(
        'Migration v${next.fromVersion} -> v${next.toVersion} overshoots the target version $toVersion.',
      );
      break;
    }
    steps.add(next);
    currentVersion = next.toVersion;
  }

  if (problems.isNotEmpty) {
    throw MigrationException(
      message: 'Invalid migration configuration:\n- ${problems.join('\n- ')}',
      code: DatumExceptionCode.migrationError,
    );
  }
  return MigrationPlan._(List.unmodifiable(steps));
}