LocalData/Documentation/Migration.md

2.6 KiB

LocalData Migration Guide

Overview

LocalData provides protocol-based migration support to move data from legacy storage locations to modern StorageKey values.

Automatic Migration

When calling get(_:) on a key, the StorageRouter automatically:

  1. Checks the primary location.
  2. If not found, evaluates migration defined on the key.
  3. If data is found in a source:
    • Unsecures it using the source's old policy.
    • Re-secures it using the destination key's policy.
    • Stores it in the new location.
    • Deletes the legacy data.
    • Returns the value.

Proactive Migration (Sweep)

You can trigger a sweep of all registered keys at app launch:

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

This iterates through all keys in the catalog and calls forceMigration(for:) on each, ensuring all legacy data is consolidated.

Defining Migration Sources

Simple Legacy Migration

For 1:1 migrations, attach a SimpleLegacyMigration:

extension StorageKey where Value == String {
    static let legacyToken = StorageKey(
        name: "old_key_name",
        domain: .userDefaults(suite: nil),
        security: .none,
        serializer: .json,
        owner: "MigrationDemo",
        description: "Legacy token stored in UserDefaults."
    )

    static let modernToken = StorageKey(
        name: "modern_token",
        domain: .keychain(service: "com.myapp"),
        owner: "MigrationDemo",
        description: "Modern token stored in Keychain.",
        migration: { key in
            AnyStorageMigration(
                SimpleLegacyMigration(
                    destinationKey: key,
                    sourceKey: .key(StorageKey.legacyToken)
                )
            )
        }
    )
}

Protocol-Based Migration

For complex scenarios, attach an explicit migration:

struct MyMigration: StorageMigration {
    typealias Value = String

    let destinationKey = StorageKey.modernToken

    func shouldMigrate(using router: StorageRouter, context: MigrationContext) async throws -> Bool {
        try await router.exists(destinationKey)
    }

    func migrate(using router: StorageRouter, context: MigrationContext) async throws -> MigrationResult {
        // Custom migration logic
        MigrationResult(success: true)
    }
}

extension StorageKey where Value == String {
    static let modernToken = StorageKey(
        name: "modern_token",
        domain: .keychain(service: "com.myapp"),
        owner: "MigrationDemo",
        description: "Modern token stored in Keychain.",
        migration: { _ in AnyStorageMigration(MyMigration()) }
    )
}