LocalData/Sources/LocalData/Documentation.docc/Migrations.md
Matt Bruce 84c277f2ec docc docs
Signed-off-by: Matt Bruce <mbrucedogs@gmail.com>
2026-01-17 10:48:31 -06:00

1.8 KiB

Migrations

LocalData supports lazy and proactive migrations to move data from legacy keys to modern keys.

Lazy migrations (on read)

Attach a migration to a key. If the destination is missing, StorageRouter.get(_:) will run it.

extension StorageKey where Value == String {
    static let legacyToken = StorageKey(
        name: "legacy_token",
        domain: .userDefaults(suite: nil),
        security: .none,
        owner: "Auth",
        description: "Legacy token."
    )

    static let userToken = StorageKey(
        name: "user_token",
        domain: .keychain(service: "com.myapp"),
        owner: "Auth",
        description: "Modern token.",
        migration: { destination in
            AnyStorageMigration(
                SimpleLegacyMigration(
                    destinationKey: destination,
                    sourceKey: .key(StorageKey.legacyToken)
                )
            )
        }
    )
}

Proactive sweeps

Run migrations at startup to drain legacy values:

try await StorageRouter.shared.registerCatalog(AuthCatalog(), migrateImmediately: true)

Transforming migrations

Use DefaultTransformingMigration when types change.

let migration = DefaultTransformingMigration(
    destinationKey: StorageKey.userAge,
    sourceKey: StorageKey.legacyAgeString
) { value in
    guard let intValue = Int(value) else {
        throw MigrationError.transformationFailed("Invalid age")
    }
    return intValue
}

Aggregating migrations

Combine multiple legacy values into one destination:

let migration = DefaultAggregatingMigration(
    destinationKey: StorageKey.profileSummary,
    sourceKeys: [.key(StorageKey.firstName), .key(StorageKey.lastName)]
) { sources in
    let parts = sources.compactMap { $0.value as? String }
    return parts.joined(separator: " ")
}