Update Migrations, Models, Protocols (+1 more)
Summary: - Sources: Migrations, Models, Protocols, Services - Added symbols: struct AppVersionConditionalMigration, struct DefaultAggregatingMigration, func aggregate, struct DefaultTransformingMigration, func transform, struct SimpleLegacyMigration (+13 more) - Removed symbols: struct AppVersionConditionalMigration, typealias DestinationKey, struct DefaultAggregatingMigration, func aggregate, struct DefaultTransformingMigration, typealias SourceKey (+16 more) Stats: - 14 files changed, 88 insertions(+), 80 deletions(-)
This commit is contained in:
parent
efb740e291
commit
1a37206c12
@ -1,15 +1,13 @@
|
||||
import Foundation
|
||||
|
||||
/// Conditional migration for app version-based migration.
|
||||
public struct AppVersionConditionalMigration<Destination: StorageKey>: ConditionalMigration {
|
||||
public typealias DestinationKey = Destination
|
||||
|
||||
public let destinationKey: Destination
|
||||
public struct AppVersionConditionalMigration<Value: Codable & Sendable>: ConditionalMigration {
|
||||
public let destinationKey: StorageKey<Value>
|
||||
public let minAppVersion: String
|
||||
public let fallbackMigration: AnyStorageMigration
|
||||
|
||||
public init(
|
||||
destinationKey: Destination,
|
||||
destinationKey: StorageKey<Value>,
|
||||
minAppVersion: String,
|
||||
fallbackMigration: AnyStorageMigration
|
||||
) {
|
||||
|
||||
@ -1,23 +1,21 @@
|
||||
import Foundation
|
||||
|
||||
public struct DefaultAggregatingMigration<Destination: StorageKey>: AggregatingMigration {
|
||||
public typealias DestinationKey = Destination
|
||||
|
||||
public let destinationKey: Destination
|
||||
public struct DefaultAggregatingMigration<Value: Codable & Sendable>: AggregatingMigration {
|
||||
public let destinationKey: StorageKey<Value>
|
||||
public let sourceKeys: [AnyStorageKey]
|
||||
public let aggregateAction: ([AnyCodable]) async throws -> Destination.Value
|
||||
public let aggregateAction: ([AnyCodable]) async throws -> Value
|
||||
|
||||
public init(
|
||||
destinationKey: Destination,
|
||||
destinationKey: StorageKey<Value>,
|
||||
sourceKeys: [AnyStorageKey],
|
||||
aggregate: @escaping ([AnyCodable]) async throws -> Destination.Value
|
||||
aggregate: @escaping ([AnyCodable]) async throws -> Value
|
||||
) {
|
||||
self.destinationKey = destinationKey
|
||||
self.sourceKeys = sourceKeys
|
||||
self.aggregateAction = aggregate
|
||||
}
|
||||
|
||||
public func aggregate(_ sources: [AnyCodable]) async throws -> Destination.Value {
|
||||
public func aggregate(_ sources: [AnyCodable]) async throws -> Value {
|
||||
try await aggregateAction(sources)
|
||||
}
|
||||
|
||||
|
||||
@ -1,24 +1,21 @@
|
||||
import Foundation
|
||||
|
||||
public struct DefaultTransformingMigration<Source: StorageKey, Destination: StorageKey>: TransformingMigration {
|
||||
public typealias SourceKey = Source
|
||||
public typealias DestinationKey = Destination
|
||||
|
||||
public let destinationKey: Destination
|
||||
public let sourceKey: Source
|
||||
public let transformAction: (Source.Value) async throws -> Destination.Value
|
||||
public struct DefaultTransformingMigration<SourceValue: Codable & Sendable, DestinationValue: Codable & Sendable>: TransformingMigration {
|
||||
public let destinationKey: StorageKey<DestinationValue>
|
||||
public let sourceKey: StorageKey<SourceValue>
|
||||
public let transformAction: (SourceValue) async throws -> DestinationValue
|
||||
|
||||
public init(
|
||||
destinationKey: Destination,
|
||||
sourceKey: Source,
|
||||
transform: @escaping (Source.Value) async throws -> Destination.Value
|
||||
destinationKey: StorageKey<DestinationValue>,
|
||||
sourceKey: StorageKey<SourceValue>,
|
||||
transform: @escaping (SourceValue) async throws -> DestinationValue
|
||||
) {
|
||||
self.destinationKey = destinationKey
|
||||
self.sourceKey = sourceKey
|
||||
self.transformAction = transform
|
||||
}
|
||||
|
||||
public func transform(_ source: Source.Value) async throws -> Destination.Value {
|
||||
public func transform(_ source: SourceValue) async throws -> DestinationValue {
|
||||
try await transformAction(source)
|
||||
}
|
||||
|
||||
|
||||
@ -1,13 +1,11 @@
|
||||
import Foundation
|
||||
|
||||
/// Simple 1:1 legacy migration.
|
||||
public struct SimpleLegacyMigration<Destination: StorageKey>: StorageMigration {
|
||||
public typealias DestinationKey = Destination
|
||||
|
||||
public let destinationKey: Destination
|
||||
public struct SimpleLegacyMigration<Value: Codable & Sendable>: StorageMigration {
|
||||
public let destinationKey: StorageKey<Value>
|
||||
public let sourceKey: AnyStorageKey
|
||||
|
||||
public init(destinationKey: Destination, sourceKey: AnyStorageKey) {
|
||||
public init(destinationKey: StorageKey<Value>, sourceKey: AnyStorageKey) {
|
||||
self.destinationKey = destinationKey
|
||||
self.sourceKey = sourceKey
|
||||
}
|
||||
|
||||
@ -3,7 +3,7 @@ public struct AnyStorageKey: Sendable {
|
||||
public internal(set) var migration: AnyStorageMigration?
|
||||
private let migrateAction: @Sendable (StorageRouter) async throws -> Void
|
||||
|
||||
public init<Key: StorageKey>(_ key: Key) {
|
||||
public init<Value>(_ key: StorageKey<Value>) {
|
||||
self.descriptor = .from(key)
|
||||
self.migration = key.migration
|
||||
self.migrateAction = { router in
|
||||
@ -21,7 +21,7 @@ public struct AnyStorageKey: Sendable {
|
||||
self.migrateAction = migrateAction
|
||||
}
|
||||
|
||||
public static func key<Key: StorageKey>(_ key: Key) -> AnyStorageKey {
|
||||
public static func key<Value>(_ key: StorageKey<Value>) -> AnyStorageKey {
|
||||
AnyStorageKey(key)
|
||||
}
|
||||
|
||||
|
||||
40
Sources/LocalData/Models/StorageKey.swift
Normal file
40
Sources/LocalData/Models/StorageKey.swift
Normal file
@ -0,0 +1,40 @@
|
||||
import Foundation
|
||||
|
||||
public struct StorageKey<Value: Codable & Sendable>: Sendable, CustomStringConvertible {
|
||||
public let name: String
|
||||
public let domain: StorageDomain
|
||||
public let security: SecurityPolicy
|
||||
public let serializer: Serializer<Value>
|
||||
public let owner: String
|
||||
public let description: String
|
||||
public let availability: PlatformAvailability
|
||||
public let syncPolicy: SyncPolicy
|
||||
|
||||
private let migrationBuilder: (@Sendable (StorageKey<Value>) -> AnyStorageMigration?)?
|
||||
|
||||
public init(
|
||||
name: String,
|
||||
domain: StorageDomain,
|
||||
security: SecurityPolicy = .recommended,
|
||||
serializer: Serializer<Value> = .json,
|
||||
owner: String,
|
||||
description: String,
|
||||
availability: PlatformAvailability = .all,
|
||||
syncPolicy: SyncPolicy = .never,
|
||||
migration: (@Sendable (StorageKey<Value>) -> AnyStorageMigration?)? = nil
|
||||
) {
|
||||
self.name = name
|
||||
self.domain = domain
|
||||
self.security = security
|
||||
self.serializer = serializer
|
||||
self.owner = owner
|
||||
self.description = description
|
||||
self.availability = availability
|
||||
self.syncPolicy = syncPolicy
|
||||
self.migrationBuilder = migration
|
||||
}
|
||||
|
||||
public var migration: AnyStorageMigration? {
|
||||
migrationBuilder?(self)
|
||||
}
|
||||
}
|
||||
@ -36,8 +36,8 @@ public struct StorageKeyDescriptor: Sendable, CustomStringConvertible {
|
||||
self.catalog = catalog
|
||||
}
|
||||
|
||||
public static func from<Key: StorageKey>(
|
||||
_ key: Key,
|
||||
public static func from<Value>(
|
||||
_ key: StorageKey<Value>,
|
||||
catalog: String? = nil
|
||||
) -> StorageKeyDescriptor {
|
||||
StorageKeyDescriptor(
|
||||
@ -45,7 +45,7 @@ public struct StorageKeyDescriptor: Sendable, CustomStringConvertible {
|
||||
domain: key.domain,
|
||||
security: key.security,
|
||||
serializer: key.serializer.name,
|
||||
valueType: String(describing: Key.Value.self),
|
||||
valueType: String(describing: Value.self),
|
||||
owner: key.owner,
|
||||
availability: key.availability,
|
||||
syncPolicy: key.syncPolicy,
|
||||
|
||||
@ -3,5 +3,5 @@ import Foundation
|
||||
/// Migration protocol that combines multiple sources into a single destination.
|
||||
public protocol AggregatingMigration: StorageMigration {
|
||||
var sourceKeys: [AnyStorageKey] { get }
|
||||
func aggregate(_ sources: [AnyCodable]) async throws -> DestinationKey.Value
|
||||
func aggregate(_ sources: [AnyCodable]) async throws -> Value
|
||||
}
|
||||
|
||||
@ -1,3 +0,0 @@
|
||||
public extension StorageKey {
|
||||
var security: SecurityPolicy { .recommended }
|
||||
}
|
||||
@ -1,20 +0,0 @@
|
||||
import Foundation
|
||||
|
||||
public protocol StorageKey: Sendable, CustomStringConvertible {
|
||||
associatedtype Value: Codable & Sendable
|
||||
|
||||
var name: String { get }
|
||||
var domain: StorageDomain { get }
|
||||
var security: SecurityPolicy { get }
|
||||
var serializer: Serializer<Value> { get }
|
||||
var owner: String { get }
|
||||
|
||||
var availability: PlatformAvailability { get }
|
||||
var syncPolicy: SyncPolicy { get }
|
||||
|
||||
var migration: AnyStorageMigration? { get }
|
||||
}
|
||||
|
||||
extension StorageKey {
|
||||
public var migration: AnyStorageMigration? { nil }
|
||||
}
|
||||
@ -2,10 +2,10 @@ import Foundation
|
||||
|
||||
/// Core migration protocol with high-level methods.
|
||||
public protocol StorageMigration: Sendable {
|
||||
associatedtype DestinationKey: StorageKey
|
||||
associatedtype Value: Codable & Sendable
|
||||
|
||||
/// The destination storage key where migrated data will be stored.
|
||||
var destinationKey: DestinationKey { get }
|
||||
var destinationKey: StorageKey<Value> { get }
|
||||
|
||||
/// Validate if migration should proceed (conditional logic).
|
||||
func shouldMigrate(using router: StorageRouter, context: MigrationContext) async throws -> Bool
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import Foundation
|
||||
|
||||
public protocol StorageProviding: Sendable {
|
||||
func set<Key: StorageKey>(_ value: Key.Value, for key: Key) async throws
|
||||
func get<Key: StorageKey>(_ key: Key) async throws -> Key.Value
|
||||
func remove<Key: StorageKey>(_ key: Key) async throws
|
||||
func set<Value>(_ value: Value, for key: StorageKey<Value>) async throws
|
||||
func get<Value>(_ key: StorageKey<Value>) async throws -> Value
|
||||
func remove<Value>(_ key: StorageKey<Value>) async throws
|
||||
}
|
||||
|
||||
@ -2,8 +2,8 @@ import Foundation
|
||||
|
||||
/// Migration protocol that supports data transformation during migration.
|
||||
public protocol TransformingMigration: StorageMigration {
|
||||
associatedtype SourceKey: StorageKey
|
||||
associatedtype SourceValue: Codable & Sendable
|
||||
|
||||
var sourceKey: SourceKey { get }
|
||||
func transform(_ source: SourceKey.Value) async throws -> DestinationKey.Value
|
||||
var sourceKey: StorageKey<SourceValue> { get }
|
||||
func transform(_ source: SourceValue) async throws -> Value
|
||||
}
|
||||
|
||||
@ -127,7 +127,7 @@ public actor StorageRouter: StorageProviding {
|
||||
}
|
||||
|
||||
/// Returns the last migration date for a specific key, if available.
|
||||
public func migrationHistory<Key: StorageKey>(for key: Key) -> Date? {
|
||||
public func migrationHistory<Value>(for key: StorageKey<Value>) -> Date? {
|
||||
migrationHistory[key.name]
|
||||
}
|
||||
|
||||
@ -138,7 +138,7 @@ public actor StorageRouter: StorageProviding {
|
||||
/// - value: The value to store.
|
||||
/// - key: The storage key defining where and how to store.
|
||||
/// - Throws: Various errors depending on the storage domain and security policy.
|
||||
public func set<Key: StorageKey>(_ value: Key.Value, for key: Key) async throws {
|
||||
public func set<Value>(_ value: Value, for key: StorageKey<Value>) async throws {
|
||||
Logger.debug(">>> [STORAGE] SET: \(key.name) [Domain: \(key.domain)]")
|
||||
try validateCatalogRegistration(for: key)
|
||||
try validatePlatformAvailability(for: key)
|
||||
@ -155,7 +155,7 @@ public actor StorageRouter: StorageProviding {
|
||||
/// - Parameter key: The storage key to retrieve.
|
||||
/// - Returns: The stored value.
|
||||
/// - Throws: `StorageError.notFound` if no value exists, plus domain-specific errors.
|
||||
public func get<Key: StorageKey>(_ key: Key) async throws -> Key.Value {
|
||||
public func get<Value>(_ key: StorageKey<Value>) async throws -> Value {
|
||||
Logger.debug(">>> [STORAGE] GET: \(key.name)")
|
||||
try validateCatalogRegistration(for: key)
|
||||
try validatePlatformAvailability(for: key)
|
||||
@ -182,7 +182,7 @@ public actor StorageRouter: StorageProviding {
|
||||
/// - Parameter key: The storage key to migrate.
|
||||
/// - Returns: The migration result.
|
||||
/// - Throws: Migration or storage errors.
|
||||
public func forceMigration<Key: StorageKey>(for key: Key) async throws -> MigrationResult {
|
||||
public func forceMigration<Value>(for key: StorageKey<Value>) async throws -> MigrationResult {
|
||||
Logger.debug(">>> [STORAGE] MANUAL MIGRATION: \(key.name)")
|
||||
try validateCatalogRegistration(for: key)
|
||||
|
||||
@ -207,7 +207,7 @@ public actor StorageRouter: StorageProviding {
|
||||
/// Removes the value for the given key.
|
||||
/// - Parameter key: The storage key to remove.
|
||||
/// - Throws: Domain-specific errors if removal fails.
|
||||
public func remove<Key: StorageKey>(_ key: Key) async throws {
|
||||
public func remove<Value>(_ key: StorageKey<Value>) async throws {
|
||||
try validateCatalogRegistration(for: key)
|
||||
try validatePlatformAvailability(for: key)
|
||||
try await delete(for: .from(key))
|
||||
@ -216,7 +216,7 @@ public actor StorageRouter: StorageProviding {
|
||||
/// Checks if a value exists for the given key.
|
||||
/// - Parameter key: The storage key to check.
|
||||
/// - Returns: True if a value exists.
|
||||
public func exists<Key: StorageKey>(_ key: Key) async throws -> Bool {
|
||||
public func exists<Value>(_ key: StorageKey<Value>) async throws -> Bool {
|
||||
try validateCatalogRegistration(for: key)
|
||||
try validatePlatformAvailability(for: key)
|
||||
|
||||
@ -265,7 +265,7 @@ public actor StorageRouter: StorageProviding {
|
||||
|
||||
// MARK: - Platform Validation
|
||||
|
||||
nonisolated private func validatePlatformAvailability<Key: StorageKey>(for key: Key) throws {
|
||||
nonisolated private func validatePlatformAvailability<Value>(for key: StorageKey<Value>) throws {
|
||||
#if os(watchOS)
|
||||
if key.availability == .phoneOnly {
|
||||
throw StorageError.phoneOnlyKeyAccessedOnWatch(key.name)
|
||||
@ -277,7 +277,7 @@ public actor StorageRouter: StorageProviding {
|
||||
#endif
|
||||
}
|
||||
|
||||
private func validateCatalogRegistration<Key: StorageKey>(for key: Key) throws {
|
||||
private func validateCatalogRegistration<Value>(for key: StorageKey<Value>) throws {
|
||||
guard !registeredKeys.isEmpty else { return }
|
||||
guard registeredKeys[key.name] != nil else {
|
||||
let errorMessage = "UNREGISTERED STORAGE KEY: '\(key.name)' (Owner: \(key.owner)) accessed but not found in any registered catalog. Did you forget to call registerCatalog?"
|
||||
@ -331,7 +331,7 @@ public actor StorageRouter: StorageProviding {
|
||||
|
||||
// MARK: - Migration
|
||||
|
||||
private func attemptMigration<Key: StorageKey>(for key: Key) async throws -> Key.Value? {
|
||||
private func attemptMigration<Value>(for key: StorageKey<Value>) async throws -> Value? {
|
||||
guard let migration = resolveMigration(for: key) else { return nil }
|
||||
|
||||
let context = buildMigrationContext()
|
||||
@ -354,7 +354,7 @@ public actor StorageRouter: StorageProviding {
|
||||
return nil
|
||||
}
|
||||
|
||||
private func resolveMigration<Key: StorageKey>(for key: Key) -> AnyStorageMigration? {
|
||||
private func resolveMigration<Value>(for key: StorageKey<Value>) -> AnyStorageMigration? {
|
||||
key.migration
|
||||
}
|
||||
|
||||
@ -366,8 +366,8 @@ public actor StorageRouter: StorageProviding {
|
||||
migrationHistory[descriptor.name] = Date()
|
||||
}
|
||||
|
||||
internal func shouldAllowMigration<Key: StorageKey>(
|
||||
for key: Key,
|
||||
internal func shouldAllowMigration<Value>(
|
||||
for key: StorageKey<Value>,
|
||||
context: MigrationContext
|
||||
) async throws -> Bool {
|
||||
guard key.availability.isAvailable(on: context.deviceInfo.platform) else {
|
||||
@ -440,7 +440,7 @@ public actor StorageRouter: StorageProviding {
|
||||
|
||||
// MARK: - Storage Operations
|
||||
|
||||
private func store(_ data: Data, for key: any StorageKey) async throws {
|
||||
private func store<Value>(_ data: Data, for key: StorageKey<Value>) async throws {
|
||||
try await store(data, for: .from(key))
|
||||
}
|
||||
|
||||
@ -544,7 +544,7 @@ public actor StorageRouter: StorageProviding {
|
||||
|
||||
// MARK: - Sync
|
||||
|
||||
private func handleSync(_ key: any StorageKey, data: Data) async throws {
|
||||
private func handleSync<Value>(_ key: StorageKey<Value>, data: Data) async throws {
|
||||
try await sync.syncIfNeeded(
|
||||
data: data,
|
||||
keyName: key.name,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user