Summary: - Sources: update Audit, Helpers, Migrations (+2 more) Stats: - 29 files changed, 471 insertions(+), 25 deletions(-)
94 lines
3.8 KiB
Swift
94 lines
3.8 KiB
Swift
import Foundation
|
|
|
|
/// Default migration that transforms a single source value into a destination value.
|
|
///
|
|
/// Use this migration when the destination value type differs from the legacy
|
|
/// type or when you need to normalize/clean legacy data before storage.
|
|
public struct DefaultTransformingMigration<SourceValue: Codable & Sendable, DestinationValue: Codable & Sendable>: TransformingMigration {
|
|
/// Destination key for the transformed value.
|
|
public let destinationKey: StorageKey<DestinationValue>
|
|
/// Source key providing the legacy value.
|
|
public let sourceKey: StorageKey<SourceValue>
|
|
/// Async transform from source to destination.
|
|
public let transformAction: @Sendable (SourceValue) async throws -> DestinationValue
|
|
|
|
/// Creates a transforming migration with a custom transform closure.
|
|
///
|
|
/// - Parameters:
|
|
/// - destinationKey: The key that receives the transformed value.
|
|
/// - sourceKey: The legacy key providing the source value.
|
|
/// - transform: Closure that converts the source value into the destination type.
|
|
public init(
|
|
destinationKey: StorageKey<DestinationValue>,
|
|
sourceKey: StorageKey<SourceValue>,
|
|
transform: @escaping @Sendable (SourceValue) async throws -> DestinationValue
|
|
) {
|
|
self.destinationKey = destinationKey
|
|
self.sourceKey = sourceKey
|
|
self.transformAction = transform
|
|
}
|
|
|
|
/// Transforms a source value into the destination type.
|
|
///
|
|
/// - Parameter source: The value read from ``sourceKey``.
|
|
/// - Returns: The transformed destination value.
|
|
public func transform(_ source: SourceValue) async throws -> DestinationValue {
|
|
try await transformAction(source)
|
|
}
|
|
|
|
/// Determines whether the migration should run.
|
|
///
|
|
/// The migration runs when:
|
|
/// - the destination is allowed on the current platform
|
|
/// - the destination does not already exist
|
|
/// - the source key contains data
|
|
///
|
|
/// - Parameters:
|
|
/// - router: The storage router used to query state.
|
|
/// - context: Migration context for conditional checks.
|
|
/// - Returns: `true` when migration should proceed.
|
|
public func shouldMigrate(using router: StorageRouter, context: MigrationContext) async throws -> Bool {
|
|
guard try await router.shouldAllowMigration(for: destinationKey, context: context) else {
|
|
return false
|
|
}
|
|
if try await router.exists(destinationKey) {
|
|
return false
|
|
}
|
|
|
|
return try await router.exists(sourceKey)
|
|
}
|
|
|
|
/// Executes the migration and returns a result.
|
|
///
|
|
/// The migration:
|
|
/// 1. Reads the source value.
|
|
/// 2. Transforms it into the destination type.
|
|
/// 3. Writes the destination value.
|
|
/// 4. Deletes the source key.
|
|
///
|
|
/// - Parameters:
|
|
/// - router: The storage router used to read and write values.
|
|
/// - context: Migration context for conditional checks.
|
|
/// - Returns: A ``MigrationResult`` describing success or failure.
|
|
public func migrate(using router: StorageRouter, context: MigrationContext) async throws -> MigrationResult {
|
|
let startTime = Date()
|
|
|
|
do {
|
|
let sourceData = try await router.get(sourceKey)
|
|
let transformedData = try await transform(sourceData)
|
|
try await router.set(transformedData, for: destinationKey)
|
|
try await router.remove(sourceKey)
|
|
|
|
let duration = Date().timeIntervalSince(startTime)
|
|
return MigrationResult(success: true, migratedCount: 1, duration: duration)
|
|
} catch {
|
|
let duration = Date().timeIntervalSince(startTime)
|
|
return MigrationResult(
|
|
success: false,
|
|
errors: [.transformationFailed(error.localizedDescription)],
|
|
duration: duration
|
|
)
|
|
}
|
|
}
|
|
}
|