Add StorageKey, updatedLogs; Remove StorageKeys, AppGroupUserDefaultsKey, Value, AppGroupUserProfileKey (+25 more)
This commit is contained in:
parent
e025e435bc
commit
6d5c916f95
@ -6,31 +6,31 @@ import SharedKit
|
|||||||
struct AppStorageCatalog: StorageKeyCatalog {
|
struct AppStorageCatalog: StorageKeyCatalog {
|
||||||
var allKeys: [AnyStorageKey] {
|
var allKeys: [AnyStorageKey] {
|
||||||
[
|
[
|
||||||
.key(StorageKeys.AppVersionKey()),
|
.key(StorageKey.appVersion),
|
||||||
.key(StorageKeys.UserPreferencesKey()),
|
.key(StorageKey.userPreferences),
|
||||||
.key(StorageKeys.CredentialsKey()),
|
.key(StorageKey.credentials),
|
||||||
.key(StorageKeys.LastLocationKey()),
|
.key(StorageKey.lastLocation),
|
||||||
.key(StorageKeys.APITokenKey()),
|
.key(StorageKey.apiToken),
|
||||||
.key(StorageKeys.UserProfileFileKey()),
|
.key(StorageKey.userProfileFile),
|
||||||
.key(StorageKeys.CachedDataKey()),
|
.key(StorageKey.cachedData),
|
||||||
.key(StorageKeys.SettingsPlistKey()),
|
.key(StorageKey.settingsPlist),
|
||||||
.key(StorageKeys.SessionLogsKey()),
|
.key(StorageKey.sessionLogs),
|
||||||
.key(StorageKeys.PrivateNotesKey()),
|
.key(StorageKey.privateNotes),
|
||||||
.key(StorageKeys.ExternalSessionLogsKey()),
|
.key(StorageKey.externalSessionLogs),
|
||||||
.key(StorageKeys.WatchVibrationKey()),
|
.key(StorageKey.watchVibration),
|
||||||
.key(StorageKeys.SyncableSettingKey()),
|
.key(StorageKey.syncableSetting),
|
||||||
.key(StorageKeys.ExternalKeyMaterialKey()),
|
.key(StorageKey.externalKeyMaterial),
|
||||||
.key(StorageKeys.AppGroupUserDefaultsKey()),
|
.key(StorageKey.appGroupUserDefaults),
|
||||||
.key(StorageKeys.AppGroupUserProfileKey()),
|
.key(StorageKey.appGroupUserProfile),
|
||||||
.key(StorageKeys.LegacyMigrationSourceKey()),
|
.key(StorageKey.legacyMigrationSource),
|
||||||
.key(StorageKeys.ModernMigrationDestinationKey()),
|
.key(StorageKey.modernMigrationDestination),
|
||||||
.key(StorageKeys.LegacyProfileNameKey()),
|
.key(StorageKey.legacyProfileName),
|
||||||
.key(StorageKeys.ModernProfileNameKey()),
|
.key(StorageKey.modernProfileName),
|
||||||
.key(StorageKeys.LegacyNotificationSettingKey()),
|
.key(StorageKey.legacyNotificationSetting),
|
||||||
.key(StorageKeys.LegacyThemeSettingKey()),
|
.key(StorageKey.legacyThemeSetting),
|
||||||
.key(StorageKeys.ModernUnifiedSettingsKey()),
|
.key(StorageKey.modernUnifiedSettings),
|
||||||
.key(StorageKeys.LegacyAppModeKey()),
|
.key(StorageKey.legacyAppMode),
|
||||||
.key(StorageKeys.ModernAppModeKey())
|
.key(StorageKey.modernAppMode)
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,8 +9,8 @@ struct ExternalKeyMaterialProvider: KeyMaterialProviding {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func keyMaterial(for keyName: String) async throws -> Data {
|
func keyMaterial(for keyName: String) async throws -> Data {
|
||||||
let key = StorageKeys.ExternalKeyMaterialKey()
|
let key = StorageKey.externalKeyMaterial
|
||||||
if let existing = try await StorageRouter.shared.get(key) as Data? {
|
if let existing = try? await StorageRouter.shared.get(key) {
|
||||||
return existing
|
return existing
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,21 +1,20 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import LocalData
|
import LocalData
|
||||||
import SharedKit
|
import SharedKit
|
||||||
extension StorageKeys {
|
|
||||||
|
extension StorageKey where Value == String {
|
||||||
/// Stores a shared setting in App Group UserDefaults.
|
/// Stores a shared setting in App Group UserDefaults.
|
||||||
/// - Domain: App Group UserDefaults
|
/// - Domain: App Group UserDefaults
|
||||||
/// - Security: None
|
/// - Security: None
|
||||||
/// - Sync: Never
|
/// - Sync: Never
|
||||||
struct AppGroupUserDefaultsKey: StorageKey {
|
nonisolated static let appGroupUserDefaults = StorageKey(
|
||||||
typealias Value = String
|
name: "app_group_setting",
|
||||||
|
domain: .appGroupUserDefaults(identifier: StorageServiceIdentifiers.appGroupIdentifier),
|
||||||
let name = "app_group_setting"
|
security: .none,
|
||||||
let domain: StorageDomain = .appGroupUserDefaults(identifier: StorageServiceIdentifiers.appGroupIdentifier)
|
serializer: .json,
|
||||||
let security: SecurityPolicy = .none
|
owner: "SampleApp",
|
||||||
let serializer: Serializer<String> = .json
|
description: "Stores a shared setting readable by app extensions.",
|
||||||
let owner = "SampleApp"
|
availability: .phoneOnly,
|
||||||
let description = "Stores a shared setting readable by app extensions."
|
syncPolicy: .never
|
||||||
let availability: PlatformAvailability = .phoneOnly
|
)
|
||||||
let syncPolicy: SyncPolicy = .never
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,29 +2,40 @@ import Foundation
|
|||||||
import LocalData
|
import LocalData
|
||||||
import SharedKit
|
import SharedKit
|
||||||
|
|
||||||
extension StorageKeys {
|
extension StorageKey where Value == UserProfile {
|
||||||
/// Stores a shared user profile in the App Group container.
|
/// Stores a shared user profile in the App Group container.
|
||||||
/// - Domain: App Group File System
|
/// - Domain: App Group File System
|
||||||
/// - Security: None
|
/// - Security: None
|
||||||
/// - Sync: Never
|
/// - Sync: Never
|
||||||
struct AppGroupUserProfileKey: StorageKey {
|
nonisolated static let appGroupUserProfile = StorageKey(
|
||||||
typealias Value = UserProfile
|
name: "app_group_user_profile.json",
|
||||||
|
domain: .appGroupFileSystem(
|
||||||
|
identifier: StorageServiceIdentifiers.appGroupIdentifier,
|
||||||
|
directory: .documents
|
||||||
|
),
|
||||||
|
security: .none,
|
||||||
|
serializer: .json,
|
||||||
|
owner: "SampleApp",
|
||||||
|
description: "Stores a profile shared between the app and extensions.",
|
||||||
|
availability: .phoneOnly,
|
||||||
|
syncPolicy: .never
|
||||||
|
)
|
||||||
|
|
||||||
let directory: FileDirectory
|
nonisolated static func appGroupUserProfileKey(
|
||||||
let name = "app_group_user_profile.json"
|
directory: FileDirectory = .documents
|
||||||
let security: SecurityPolicy = .none
|
) -> StorageKey {
|
||||||
let serializer: Serializer<UserProfile> = .json
|
StorageKey(
|
||||||
let owner = "SampleApp"
|
name: "app_group_user_profile.json",
|
||||||
let description = "Stores a profile shared between the app and extensions."
|
domain: .appGroupFileSystem(
|
||||||
let availability: PlatformAvailability = .phoneOnly
|
identifier: StorageServiceIdentifiers.appGroupIdentifier,
|
||||||
let syncPolicy: SyncPolicy = .never
|
directory: directory
|
||||||
|
),
|
||||||
init(directory: FileDirectory = .documents) {
|
security: .none,
|
||||||
self.directory = directory
|
serializer: .json,
|
||||||
}
|
owner: "SampleApp",
|
||||||
|
description: "Stores a profile shared between the app and extensions.",
|
||||||
var domain: StorageDomain {
|
availability: .phoneOnly,
|
||||||
.appGroupFileSystem(identifier: StorageServiceIdentifiers.appGroupIdentifier, directory: directory)
|
syncPolicy: .never
|
||||||
}
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,21 +2,19 @@ import Foundation
|
|||||||
import LocalData
|
import LocalData
|
||||||
import SharedKit
|
import SharedKit
|
||||||
|
|
||||||
extension StorageKeys {
|
extension StorageKey where Value == [String: AnyCodable] {
|
||||||
/// Stores user preferences in App Group UserDefaults.
|
/// Stores user preferences in App Group UserDefaults.
|
||||||
/// - Domain: App Group UserDefaults
|
/// - Domain: App Group UserDefaults
|
||||||
/// - Security: None
|
/// - Security: None
|
||||||
/// - Sync: Never
|
/// - Sync: Never
|
||||||
struct UserPreferencesKey: StorageKey {
|
nonisolated static let userPreferences = StorageKey(
|
||||||
typealias Value = [String: AnyCodable]
|
name: "user_preferences",
|
||||||
|
domain: .appGroupUserDefaults(identifier: StorageServiceIdentifiers.appGroupIdentifier),
|
||||||
let name = "user_preferences"
|
security: .none,
|
||||||
let domain: StorageDomain = .appGroupUserDefaults(identifier: StorageServiceIdentifiers.appGroupIdentifier)
|
serializer: .json,
|
||||||
let security: SecurityPolicy = .none
|
owner: "SampleApp",
|
||||||
let serializer: Serializer<[String: AnyCodable]> = .json
|
description: "Stores shared user preferences for app configuration screens.",
|
||||||
let owner = "SampleApp"
|
availability: .all,
|
||||||
let description = "Stores shared user preferences for app configuration screens."
|
syncPolicy: .never
|
||||||
let availability: PlatformAvailability = .all
|
)
|
||||||
let syncPolicy: SyncPolicy = .never
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,20 +1,16 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import LocalData
|
import LocalData
|
||||||
|
|
||||||
extension StorageKeys {
|
extension StorageKey where Value == [String] {
|
||||||
/// Stores session logs with encryption using external key material.
|
/// Stores session logs with encryption using external key material.
|
||||||
struct ExternalSessionLogsKey: StorageKey {
|
nonisolated static let externalSessionLogs = StorageKey(
|
||||||
typealias Value = [String]
|
name: "external_session_logs.json",
|
||||||
|
domain: .encryptedFileSystem(directory: .caches),
|
||||||
let name = "external_session_logs.json"
|
security: .encrypted(.external(source: SampleKeyMaterialSources.external)),
|
||||||
let domain: StorageDomain = .encryptedFileSystem(directory: .caches)
|
serializer: .json,
|
||||||
let security: SecurityPolicy = .encrypted(
|
owner: "SampleApp",
|
||||||
.external(source: SampleKeyMaterialSources.external)
|
description: "Stores session logs encrypted with external key material.",
|
||||||
)
|
availability: .phoneOnly,
|
||||||
let serializer: Serializer<[String]> = .json
|
syncPolicy: .never
|
||||||
let owner = "SampleApp"
|
)
|
||||||
let description = "Stores session logs encrypted with external key material."
|
|
||||||
let availability: PlatformAvailability = .phoneOnly
|
|
||||||
let syncPolicy: SyncPolicy = .never
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,20 +1,16 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import LocalData
|
import LocalData
|
||||||
|
|
||||||
extension StorageKeys {
|
extension StorageKey where Value == String {
|
||||||
/// Stores private notes with encryption.
|
/// Stores private notes with encryption.
|
||||||
struct PrivateNotesKey: StorageKey {
|
nonisolated static let privateNotes = StorageKey(
|
||||||
typealias Value = String
|
name: "private_notes.enc",
|
||||||
|
domain: .encryptedFileSystem(directory: .documents),
|
||||||
let name = "private_notes.enc"
|
security: .encrypted(.aes256(keyDerivation: .pbkdf2(iterations: 50_000))),
|
||||||
let domain: StorageDomain = .encryptedFileSystem(directory: .documents)
|
serializer: .json,
|
||||||
let security: SecurityPolicy = .encrypted(
|
owner: "SampleApp",
|
||||||
.aes256(keyDerivation: .pbkdf2(iterations: 50_000))
|
description: "Stores private notes encrypted at rest.",
|
||||||
)
|
availability: .phoneOnly,
|
||||||
let serializer: Serializer<String> = .json
|
syncPolicy: .never
|
||||||
let owner = "SampleApp"
|
)
|
||||||
let description = "Stores private notes encrypted at rest."
|
|
||||||
let availability: PlatformAvailability = .phoneOnly
|
|
||||||
let syncPolicy: SyncPolicy = .never
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,24 +1,30 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import LocalData
|
import LocalData
|
||||||
|
|
||||||
extension StorageKeys {
|
extension StorageKey where Value == [String] {
|
||||||
/// Stores session logs with full encryption.
|
/// Stores session logs with full encryption.
|
||||||
/// Configurable PBKDF2 iterations.
|
/// Configurable PBKDF2 iterations.
|
||||||
struct SessionLogsKey: StorageKey {
|
nonisolated static let sessionLogs = StorageKey(
|
||||||
typealias Value = [String]
|
name: "session_logs.json",
|
||||||
|
domain: .encryptedFileSystem(directory: .caches),
|
||||||
|
security: .encrypted(.aes256(keyDerivation: .pbkdf2(iterations: 10_000))),
|
||||||
|
serializer: .json,
|
||||||
|
owner: "SampleApp",
|
||||||
|
description: "Stores session logs encrypted with PBKDF2-derived keys.",
|
||||||
|
availability: .phoneOnly,
|
||||||
|
syncPolicy: .never
|
||||||
|
)
|
||||||
|
|
||||||
let name = "session_logs.json"
|
nonisolated static func sessionLogsKey(iterations: Int) -> StorageKey {
|
||||||
let domain: StorageDomain = .encryptedFileSystem(directory: .caches)
|
StorageKey(
|
||||||
let security: SecurityPolicy
|
name: "session_logs.json",
|
||||||
let serializer: Serializer<[String]> = .json
|
domain: .encryptedFileSystem(directory: .caches),
|
||||||
let owner = "SampleApp"
|
security: .encrypted(.aes256(keyDerivation: .pbkdf2(iterations: iterations))),
|
||||||
let description = "Stores session logs encrypted with PBKDF2-derived keys."
|
serializer: .json,
|
||||||
let availability: PlatformAvailability = .phoneOnly
|
owner: "SampleApp",
|
||||||
let syncPolicy: SyncPolicy = .never
|
description: "Stores session logs encrypted with PBKDF2-derived keys.",
|
||||||
|
availability: .phoneOnly,
|
||||||
init(iterations: Int = 10_000) {
|
syncPolicy: .never
|
||||||
// NOTE: PBKDF2 iterations must remain stable for existing data; changing this breaks decryption.
|
)
|
||||||
self.security = .encrypted(.aes256(keyDerivation: .pbkdf2(iterations: iterations)))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,18 +1,16 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import LocalData
|
import LocalData
|
||||||
|
|
||||||
extension StorageKeys {
|
extension StorageKey where Value == Data {
|
||||||
/// Stores cached data files.
|
/// Stores cached data files.
|
||||||
struct CachedDataKey: StorageKey {
|
nonisolated static let cachedData = StorageKey(
|
||||||
typealias Value = Data
|
name: "cached_data.bin",
|
||||||
|
domain: .fileSystem(directory: .caches),
|
||||||
let name = "cached_data.bin"
|
security: .none,
|
||||||
let domain: StorageDomain = .fileSystem(directory: .caches)
|
serializer: .data,
|
||||||
let security: SecurityPolicy = .none
|
owner: "SampleApp",
|
||||||
let serializer: Serializer<Data> = .data
|
description: "Stores cached binary data that can be regenerated.",
|
||||||
let owner = "SampleApp"
|
availability: .all,
|
||||||
let description = "Stores cached binary data that can be regenerated."
|
syncPolicy: .never
|
||||||
let availability: PlatformAvailability = .all
|
)
|
||||||
let syncPolicy: SyncPolicy = .never
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,15 +1,13 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import LocalData
|
import LocalData
|
||||||
|
|
||||||
extension StorageKeys {
|
extension StorageKey where Value == String {
|
||||||
/// Example using custom serializer for specialized encoding.
|
/// Example using custom serializer for specialized encoding.
|
||||||
struct CustomEncodedKey: StorageKey {
|
nonisolated static let customEncoded = StorageKey(
|
||||||
typealias Value = String
|
name: "custom_encoded",
|
||||||
|
domain: .fileSystem(directory: .documents),
|
||||||
let name = "custom_encoded"
|
security: .none,
|
||||||
let domain: StorageDomain = .fileSystem(directory: .documents)
|
serializer: .custom(
|
||||||
let security: SecurityPolicy = .none
|
|
||||||
let serializer: Serializer<String> = .custom(
|
|
||||||
encode: { value in
|
encode: { value in
|
||||||
Data(value.utf8).base64EncodedData()
|
Data(value.utf8).base64EncodedData()
|
||||||
},
|
},
|
||||||
@ -20,10 +18,10 @@ extension StorageKeys {
|
|||||||
}
|
}
|
||||||
return string
|
return string
|
||||||
}
|
}
|
||||||
)
|
),
|
||||||
let owner = "SampleApp"
|
owner: "SampleApp",
|
||||||
let description = "Stores custom-encoded string data (Base64 example)."
|
description: "Stores custom-encoded string data (Base64 example).",
|
||||||
let availability: PlatformAvailability = .all
|
availability: .all,
|
||||||
let syncPolicy: SyncPolicy = .never
|
syncPolicy: .never
|
||||||
}
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,18 +1,16 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import LocalData
|
import LocalData
|
||||||
|
|
||||||
extension StorageKeys {
|
extension StorageKey where Value == [String: AnyCodable] {
|
||||||
/// Stores settings as property list.
|
/// Stores settings as property list.
|
||||||
struct SettingsPlistKey: StorageKey {
|
nonisolated static let settingsPlist = StorageKey(
|
||||||
typealias Value = [String: AnyCodable]
|
name: "settings.plist",
|
||||||
|
domain: .fileSystem(directory: .documents),
|
||||||
let name = "settings.plist"
|
security: .none,
|
||||||
let domain: StorageDomain = .fileSystem(directory: .documents)
|
serializer: .plist,
|
||||||
let security: SecurityPolicy = .none
|
owner: "SampleApp",
|
||||||
let serializer: Serializer<[String: AnyCodable]> = .plist
|
description: "Stores app settings exported as a property list.",
|
||||||
let owner = "SampleApp"
|
availability: .all,
|
||||||
let description = "Stores app settings exported as a property list."
|
syncPolicy: .never
|
||||||
let availability: PlatformAvailability = .all
|
)
|
||||||
let syncPolicy: SyncPolicy = .never
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,22 +2,29 @@ import Foundation
|
|||||||
import LocalData
|
import LocalData
|
||||||
import SharedKit
|
import SharedKit
|
||||||
|
|
||||||
extension StorageKeys {
|
extension StorageKey where Value == UserProfile {
|
||||||
/// Stores user profile as JSON file in documents.
|
/// Stores user profile as JSON file in documents.
|
||||||
struct UserProfileFileKey: StorageKey {
|
nonisolated static let userProfileFile = StorageKey(
|
||||||
typealias Value = UserProfile
|
name: UserProfile.storageKeyName,
|
||||||
|
domain: .fileSystem(directory: .documents),
|
||||||
|
security: .none,
|
||||||
|
serializer: .json,
|
||||||
|
owner: "SampleApp",
|
||||||
|
description: "Stores a lightweight user profile for file storage and watch sync.",
|
||||||
|
availability: .phoneWithWatchSync,
|
||||||
|
syncPolicy: .automaticSmall
|
||||||
|
)
|
||||||
|
|
||||||
let name = UserProfile.storageKeyName
|
nonisolated static func userProfileFileKey(directory: FileDirectory = .documents) -> StorageKey {
|
||||||
let domain: StorageDomain
|
StorageKey(
|
||||||
let security: SecurityPolicy = .none
|
name: UserProfile.storageKeyName,
|
||||||
let serializer: Serializer<UserProfile> = .json
|
domain: .fileSystem(directory: directory),
|
||||||
let owner = "SampleApp"
|
security: .none,
|
||||||
let description = "Stores a lightweight user profile for file storage and watch sync."
|
serializer: .json,
|
||||||
let availability: PlatformAvailability = .phoneWithWatchSync
|
owner: "SampleApp",
|
||||||
let syncPolicy: SyncPolicy = .automaticSmall
|
description: "Stores a lightweight user profile for file storage and watch sync.",
|
||||||
|
availability: .phoneWithWatchSync,
|
||||||
init(directory: FileDirectory = .documents) {
|
syncPolicy: .automaticSmall
|
||||||
self.domain = .fileSystem(directory: directory)
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,21 +2,19 @@ import Foundation
|
|||||||
import LocalData
|
import LocalData
|
||||||
import SharedKit
|
import SharedKit
|
||||||
|
|
||||||
extension StorageKeys {
|
extension StorageKey where Value == String {
|
||||||
/// Stores API token in keychain.
|
/// Stores API token in keychain.
|
||||||
struct APITokenKey: StorageKey {
|
nonisolated static let apiToken = StorageKey(
|
||||||
typealias Value = String
|
name: "api_token",
|
||||||
|
domain: .keychain(service: StorageServiceIdentifiers.keychainAPIToken),
|
||||||
let name = "api_token"
|
security: .keychain(
|
||||||
let domain: StorageDomain = .keychain(service: StorageServiceIdentifiers.keychainAPIToken)
|
|
||||||
let security: SecurityPolicy = .keychain(
|
|
||||||
accessibility: .whenUnlockedThisDeviceOnly,
|
accessibility: .whenUnlockedThisDeviceOnly,
|
||||||
accessControl: nil
|
accessControl: nil
|
||||||
)
|
),
|
||||||
let serializer: Serializer<String> = .json
|
serializer: .json,
|
||||||
let owner = "SampleApp"
|
owner: "SampleApp",
|
||||||
let description = "Stores API auth token for network requests."
|
description: "Stores API auth token for network requests.",
|
||||||
let availability: PlatformAvailability = .phoneOnly
|
availability: .phoneOnly,
|
||||||
let syncPolicy: SyncPolicy = .never
|
syncPolicy: .never
|
||||||
}
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,23 +2,33 @@ import Foundation
|
|||||||
import LocalData
|
import LocalData
|
||||||
import SharedKit
|
import SharedKit
|
||||||
|
|
||||||
extension StorageKeys {
|
extension StorageKey where Value == Credential {
|
||||||
/// Stores user credentials securely in keychain.
|
/// Stores user credentials securely in keychain.
|
||||||
/// Configurable accessibility and access control.
|
/// Configurable accessibility and access control.
|
||||||
struct CredentialsKey: StorageKey {
|
nonisolated static let credentials = StorageKey(
|
||||||
typealias Value = Credential
|
name: "user_credentials",
|
||||||
|
domain: .keychain(service: StorageServiceIdentifiers.keychainCredentials),
|
||||||
|
security: .keychain(accessibility: .afterFirstUnlock, accessControl: nil),
|
||||||
|
serializer: .json,
|
||||||
|
owner: "SampleApp",
|
||||||
|
description: "Stores user credentials for sign-in flows.",
|
||||||
|
availability: .phoneOnly,
|
||||||
|
syncPolicy: .never
|
||||||
|
)
|
||||||
|
|
||||||
let name = "user_credentials"
|
nonisolated static func credentialsKey(
|
||||||
let domain: StorageDomain = .keychain(service: StorageServiceIdentifiers.keychainCredentials)
|
accessibility: KeychainAccessibility = .afterFirstUnlock,
|
||||||
let security: SecurityPolicy
|
accessControl: KeychainAccessControl? = nil
|
||||||
let serializer: Serializer<Credential> = .json
|
) -> StorageKey {
|
||||||
let owner = "SampleApp"
|
StorageKey(
|
||||||
let description = "Stores user credentials for sign-in flows."
|
name: "user_credentials",
|
||||||
let availability: PlatformAvailability = .phoneOnly
|
domain: .keychain(service: StorageServiceIdentifiers.keychainCredentials),
|
||||||
let syncPolicy: SyncPolicy = .never
|
security: .keychain(accessibility: accessibility, accessControl: accessControl),
|
||||||
|
serializer: .json,
|
||||||
init(accessibility: KeychainAccessibility = .afterFirstUnlock, accessControl: KeychainAccessControl? = nil) {
|
owner: "SampleApp",
|
||||||
self.security = .keychain(accessibility: accessibility, accessControl: accessControl)
|
description: "Stores user credentials for sign-in flows.",
|
||||||
}
|
availability: .phoneOnly,
|
||||||
|
syncPolicy: .never
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,21 +2,19 @@ import Foundation
|
|||||||
import LocalData
|
import LocalData
|
||||||
import SharedKit
|
import SharedKit
|
||||||
|
|
||||||
extension StorageKeys {
|
extension StorageKey where Value == Data {
|
||||||
/// Stores external key material used for encryption policies.
|
/// Stores external key material used for encryption policies.
|
||||||
struct ExternalKeyMaterialKey: StorageKey {
|
nonisolated static let externalKeyMaterial = StorageKey(
|
||||||
typealias Value = Data
|
name: "external_key_material",
|
||||||
|
domain: .keychain(service: StorageServiceIdentifiers.keychainExternalKeyMaterial),
|
||||||
let name = "external_key_material"
|
security: .keychain(
|
||||||
let domain: StorageDomain = .keychain(service: StorageServiceIdentifiers.keychainExternalKeyMaterial)
|
|
||||||
let security: SecurityPolicy = .keychain(
|
|
||||||
accessibility: .afterFirstUnlock,
|
accessibility: .afterFirstUnlock,
|
||||||
accessControl: nil
|
accessControl: nil
|
||||||
)
|
),
|
||||||
let serializer: Serializer<Data> = .data
|
serializer: .data,
|
||||||
let owner = "SampleApp"
|
owner: "SampleApp",
|
||||||
let description = "Stores external key material used by encryption policies."
|
description: "Stores external key material used by encryption policies.",
|
||||||
let availability: PlatformAvailability = .phoneOnly
|
availability: .phoneOnly,
|
||||||
let syncPolicy: SyncPolicy = .never
|
syncPolicy: .never
|
||||||
}
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,21 +2,19 @@ import Foundation
|
|||||||
import LocalData
|
import LocalData
|
||||||
import SharedKit
|
import SharedKit
|
||||||
|
|
||||||
extension StorageKeys {
|
extension StorageKey where Value == SampleLocationData {
|
||||||
/// Stores sensitive location data in keychain with biometric protection.
|
/// Stores sensitive location data in keychain with biometric protection.
|
||||||
struct LastLocationKey: StorageKey {
|
nonisolated static let lastLocation = StorageKey(
|
||||||
typealias Value = SampleLocationData
|
name: "last_known_location",
|
||||||
|
domain: .keychain(service: StorageServiceIdentifiers.keychainLocation),
|
||||||
let name = "last_known_location"
|
security: .keychain(
|
||||||
let domain: StorageDomain = .keychain(service: StorageServiceIdentifiers.keychainLocation)
|
|
||||||
let security: SecurityPolicy = .keychain(
|
|
||||||
accessibility: .afterFirstUnlock,
|
accessibility: .afterFirstUnlock,
|
||||||
accessControl: .userPresence
|
accessControl: .userPresence
|
||||||
)
|
),
|
||||||
let serializer: Serializer<SampleLocationData> = .json
|
serializer: .json,
|
||||||
let owner = "SampleApp"
|
owner: "SampleApp",
|
||||||
let description = "Stores last known location for location-aware features."
|
description: "Stores last known location for location-aware features.",
|
||||||
let availability: PlatformAvailability = .phoneOnly
|
availability: .phoneOnly,
|
||||||
let syncPolicy: SyncPolicy = .never
|
syncPolicy: .never
|
||||||
}
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,54 +1,51 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import LocalData
|
import LocalData
|
||||||
|
|
||||||
extension StorageKeys {
|
extension StorageKey where Value == Bool {
|
||||||
struct LegacyNotificationSettingKey: StorageKey {
|
nonisolated static let legacyNotificationSetting = StorageKey(
|
||||||
typealias Value = Bool
|
name: "legacy_notification_setting",
|
||||||
|
domain: .userDefaults(suite: nil),
|
||||||
|
security: .none,
|
||||||
|
serializer: .json,
|
||||||
|
owner: "MigrationDemo",
|
||||||
|
description: "Legacy notification setting stored as Bool.",
|
||||||
|
availability: .all,
|
||||||
|
syncPolicy: .never
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
let name = "legacy_notification_setting"
|
extension StorageKey where Value == String {
|
||||||
let domain: StorageDomain = .userDefaults(suite: nil)
|
nonisolated static let legacyThemeSetting = StorageKey(
|
||||||
let security: SecurityPolicy = .none
|
name: "legacy_theme_setting",
|
||||||
let serializer: Serializer<Bool> = .json
|
domain: .userDefaults(suite: nil),
|
||||||
let owner = "MigrationDemo"
|
security: .none,
|
||||||
let description = "Legacy notification setting stored as Bool."
|
serializer: .json,
|
||||||
let availability: PlatformAvailability = .all
|
owner: "MigrationDemo",
|
||||||
let syncPolicy: SyncPolicy = .never
|
description: "Legacy theme setting stored as a string.",
|
||||||
}
|
availability: .all,
|
||||||
|
syncPolicy: .never
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
struct LegacyThemeSettingKey: StorageKey {
|
extension StorageKey where Value == UnifiedSettings {
|
||||||
typealias Value = String
|
nonisolated static let modernUnifiedSettings = StorageKey(
|
||||||
|
name: "modern_unified_settings",
|
||||||
let name = "legacy_theme_setting"
|
domain: .fileSystem(directory: .documents),
|
||||||
let domain: StorageDomain = .userDefaults(suite: nil)
|
security: .none,
|
||||||
let security: SecurityPolicy = .none
|
serializer: .json,
|
||||||
let serializer: Serializer<String> = .json
|
owner: "MigrationDemo",
|
||||||
let owner = "MigrationDemo"
|
description: "Modern unified settings aggregated from legacy keys.",
|
||||||
let description = "Legacy theme setting stored as a string."
|
availability: .all,
|
||||||
let availability: PlatformAvailability = .all
|
syncPolicy: .never,
|
||||||
let syncPolicy: SyncPolicy = .never
|
migration: { key in
|
||||||
}
|
|
||||||
|
|
||||||
struct ModernUnifiedSettingsKey: StorageKey {
|
|
||||||
typealias Value = UnifiedSettings
|
|
||||||
|
|
||||||
let name = "modern_unified_settings"
|
|
||||||
let domain: StorageDomain = .fileSystem(directory: .documents)
|
|
||||||
let security: SecurityPolicy = .none
|
|
||||||
let serializer: Serializer<UnifiedSettings> = .json
|
|
||||||
let owner = "MigrationDemo"
|
|
||||||
let description = "Modern unified settings aggregated from legacy keys."
|
|
||||||
let availability: PlatformAvailability = .all
|
|
||||||
let syncPolicy: SyncPolicy = .never
|
|
||||||
|
|
||||||
var migration: AnyStorageMigration? {
|
|
||||||
let sources: [AnyStorageKey] = [
|
let sources: [AnyStorageKey] = [
|
||||||
.key(StorageKeys.LegacyNotificationSettingKey()),
|
.key(StorageKey<Bool>.legacyNotificationSetting),
|
||||||
.key(StorageKeys.LegacyThemeSettingKey())
|
.key(StorageKey<String>.legacyThemeSetting)
|
||||||
]
|
]
|
||||||
|
|
||||||
return AnyStorageMigration(
|
return AnyStorageMigration(
|
||||||
DefaultAggregatingMigration(
|
DefaultAggregatingMigration(
|
||||||
destinationKey: self,
|
destinationKey: key,
|
||||||
sourceKeys: sources
|
sourceKeys: sources
|
||||||
) { sources in
|
) { sources in
|
||||||
var notificationsEnabled = false
|
var notificationsEnabled = false
|
||||||
@ -69,5 +66,5 @@ extension StorageKeys {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,49 +1,43 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import LocalData
|
import LocalData
|
||||||
|
|
||||||
extension StorageKeys {
|
extension StorageKey where Value == String {
|
||||||
struct LegacyAppModeKey: StorageKey {
|
nonisolated static let legacyAppMode = StorageKey(
|
||||||
typealias Value = String
|
name: "legacy_app_mode",
|
||||||
|
domain: .userDefaults(suite: nil),
|
||||||
|
security: .none,
|
||||||
|
serializer: .json,
|
||||||
|
owner: "MigrationDemo",
|
||||||
|
description: "Legacy app mode stored in UserDefaults.",
|
||||||
|
availability: .all,
|
||||||
|
syncPolicy: .never
|
||||||
|
)
|
||||||
|
|
||||||
let name = "legacy_app_mode"
|
nonisolated static let modernAppMode = StorageKey(
|
||||||
let domain: StorageDomain = .userDefaults(suite: nil)
|
name: "modern_app_mode",
|
||||||
let security: SecurityPolicy = .none
|
domain: .keychain(service: "com.mbrucedogs.securestorage"),
|
||||||
let serializer: Serializer<String> = .json
|
security: .keychain(
|
||||||
let owner = "MigrationDemo"
|
|
||||||
let description = "Legacy app mode stored in UserDefaults."
|
|
||||||
let availability: PlatformAvailability = .all
|
|
||||||
let syncPolicy: SyncPolicy = .never
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ModernAppModeKey: StorageKey {
|
|
||||||
typealias Value = String
|
|
||||||
|
|
||||||
let name = "modern_app_mode"
|
|
||||||
let domain: StorageDomain = .keychain(service: "com.mbrucedogs.securestorage")
|
|
||||||
let security: SecurityPolicy = .keychain(
|
|
||||||
accessibility: .afterFirstUnlock,
|
accessibility: .afterFirstUnlock,
|
||||||
accessControl: .userPresence
|
accessControl: .userPresence
|
||||||
)
|
),
|
||||||
let serializer: Serializer<String> = .json
|
serializer: .json,
|
||||||
let owner = "MigrationDemo"
|
owner: "MigrationDemo",
|
||||||
let description = "Modern app mode with conditional migration."
|
description: "Modern app mode with conditional migration.",
|
||||||
let availability: PlatformAvailability = .all
|
availability: .all,
|
||||||
let syncPolicy: SyncPolicy = .never
|
syncPolicy: .never,
|
||||||
|
migration: { key in
|
||||||
var migration: AnyStorageMigration? {
|
let legacy = StorageKey.legacyAppMode
|
||||||
let destination = self
|
|
||||||
let legacy = StorageKeys.LegacyAppModeKey()
|
|
||||||
let fallback = AnyStorageMigration(
|
let fallback = AnyStorageMigration(
|
||||||
SimpleLegacyMigration(destinationKey: destination, sourceKey: .key(legacy))
|
SimpleLegacyMigration(destinationKey: key, sourceKey: .key(legacy))
|
||||||
)
|
)
|
||||||
|
|
||||||
return AnyStorageMigration(
|
return AnyStorageMigration(
|
||||||
AppVersionConditionalMigration(
|
AppVersionConditionalMigration(
|
||||||
destinationKey: destination,
|
destinationKey: key,
|
||||||
minAppVersion: "99.0",
|
minAppVersion: "99.0",
|
||||||
fallbackMigration: fallback
|
fallbackMigration: fallback
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,44 +1,39 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import LocalData
|
import LocalData
|
||||||
|
|
||||||
extension StorageKeys {
|
extension StorageKey where Value == String {
|
||||||
/// The legacy key where data starts (in UserDefaults)
|
/// The legacy key where data starts (in UserDefaults)
|
||||||
struct LegacyMigrationSourceKey: StorageKey {
|
nonisolated static let legacyMigrationSource = StorageKey(
|
||||||
typealias Value = String
|
name: "legacy_user_id",
|
||||||
|
domain: .userDefaults(suite: nil),
|
||||||
let name = "legacy_user_id"
|
security: .none,
|
||||||
let domain: StorageDomain = .userDefaults(suite: nil)
|
serializer: .json,
|
||||||
let security: SecurityPolicy = .none
|
owner: "MigrationDemo",
|
||||||
let serializer: Serializer<String> = .json
|
description: "Legacy key in UserDefaults from an older version of the app.",
|
||||||
let owner = "MigrationDemo"
|
availability: .all,
|
||||||
let description = "Legacy key in UserDefaults from an older version of the app."
|
syncPolicy: .never
|
||||||
let availability: PlatformAvailability = .all
|
)
|
||||||
let syncPolicy: SyncPolicy = .never
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The modern key where data should end up (in Keychain)
|
/// The modern key where data should end up (in Keychain)
|
||||||
struct ModernMigrationDestinationKey: StorageKey {
|
nonisolated static let modernMigrationDestination = StorageKey(
|
||||||
typealias Value = String
|
name: "secure_user_id",
|
||||||
|
domain: .keychain(service: "com.mbrucedogs.securestorage"),
|
||||||
let name = "secure_user_id"
|
security: .keychain(
|
||||||
let domain: StorageDomain = .keychain(service: "com.mbrucedogs.securestorage")
|
|
||||||
let security: SecurityPolicy = .keychain(
|
|
||||||
accessibility: .afterFirstUnlock,
|
accessibility: .afterFirstUnlock,
|
||||||
accessControl: .userPresence
|
accessControl: .userPresence
|
||||||
)
|
),
|
||||||
let serializer: Serializer<String> = .json
|
serializer: .json,
|
||||||
let owner = "MigrationDemo"
|
owner: "MigrationDemo",
|
||||||
let description = "Modern key in Keychain with biometric security."
|
description: "Modern key in Keychain with biometric security.",
|
||||||
let availability: PlatformAvailability = .all
|
availability: .all,
|
||||||
let syncPolicy: SyncPolicy = .never
|
syncPolicy: .never,
|
||||||
|
migration: { key in
|
||||||
var migration: AnyStorageMigration? {
|
|
||||||
AnyStorageMigration(
|
AnyStorageMigration(
|
||||||
SimpleLegacyMigration(
|
SimpleLegacyMigration(
|
||||||
destinationKey: self,
|
destinationKey: key,
|
||||||
sourceKey: .key(LegacyMigrationSourceKey())
|
sourceKey: .key(StorageKey.legacyMigrationSource)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,40 +1,37 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import LocalData
|
import LocalData
|
||||||
|
|
||||||
extension StorageKeys {
|
extension StorageKey where Value == String {
|
||||||
struct LegacyProfileNameKey: StorageKey {
|
nonisolated static let legacyProfileName = StorageKey(
|
||||||
typealias Value = String
|
name: "legacy_profile_name",
|
||||||
|
domain: .userDefaults(suite: nil),
|
||||||
|
security: .none,
|
||||||
|
serializer: .json,
|
||||||
|
owner: "MigrationDemo",
|
||||||
|
description: "Legacy profile name stored as a single string.",
|
||||||
|
availability: .all,
|
||||||
|
syncPolicy: .never
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
let name = "legacy_profile_name"
|
extension StorageKey where Value == ProfileName {
|
||||||
let domain: StorageDomain = .userDefaults(suite: nil)
|
nonisolated static let modernProfileName = StorageKey(
|
||||||
let security: SecurityPolicy = .none
|
name: "modern_profile_name",
|
||||||
let serializer: Serializer<String> = .json
|
domain: .keychain(service: "com.mbrucedogs.securestorage"),
|
||||||
let owner = "MigrationDemo"
|
security: .keychain(
|
||||||
let description = "Legacy profile name stored as a single string."
|
|
||||||
let availability: PlatformAvailability = .all
|
|
||||||
let syncPolicy: SyncPolicy = .never
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ModernProfileNameKey: StorageKey {
|
|
||||||
typealias Value = ProfileName
|
|
||||||
|
|
||||||
let name = "modern_profile_name"
|
|
||||||
let domain: StorageDomain = .keychain(service: "com.mbrucedogs.securestorage")
|
|
||||||
let security: SecurityPolicy = .keychain(
|
|
||||||
accessibility: .afterFirstUnlock,
|
accessibility: .afterFirstUnlock,
|
||||||
accessControl: .userPresence
|
accessControl: .userPresence
|
||||||
)
|
),
|
||||||
let serializer: Serializer<ProfileName> = .json
|
serializer: .json,
|
||||||
let owner = "MigrationDemo"
|
owner: "MigrationDemo",
|
||||||
let description = "Modern profile name stored as structured data."
|
description: "Modern profile name stored as structured data.",
|
||||||
let availability: PlatformAvailability = .all
|
availability: .all,
|
||||||
let syncPolicy: SyncPolicy = .never
|
syncPolicy: .never,
|
||||||
|
migration: { key in
|
||||||
var migration: AnyStorageMigration? {
|
let sourceKey = StorageKey<String>.legacyProfileName
|
||||||
let sourceKey = StorageKeys.LegacyProfileNameKey()
|
|
||||||
return AnyStorageMigration(
|
return AnyStorageMigration(
|
||||||
DefaultTransformingMigration(
|
DefaultTransformingMigration(
|
||||||
destinationKey: self,
|
destinationKey: key,
|
||||||
sourceKey: sourceKey
|
sourceKey: sourceKey
|
||||||
) { value in
|
) { value in
|
||||||
let parts = value.split(separator: " ", maxSplits: 1).map(String.init)
|
let parts = value.split(separator: " ", maxSplits: 1).map(String.init)
|
||||||
@ -44,5 +41,5 @@ extension StorageKeys {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,24 +1,33 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import LocalData
|
import LocalData
|
||||||
|
|
||||||
extension StorageKeys {
|
extension StorageKey where Value == String {
|
||||||
/// Syncable setting with configurable platform and sync policy.
|
/// Syncable setting with configurable platform and sync policy.
|
||||||
/// Grouped under Platform to highlight availability/sync behavior.
|
/// Grouped under Platform to highlight availability/sync behavior.
|
||||||
struct SyncableSettingKey: StorageKey {
|
nonisolated static let syncableSetting = StorageKey(
|
||||||
typealias Value = String
|
name: "syncable_setting",
|
||||||
|
domain: .userDefaults(suite: nil),
|
||||||
|
security: .none,
|
||||||
|
serializer: .json,
|
||||||
|
owner: "SampleApp",
|
||||||
|
description: "Stores a setting that can be synced to watch.",
|
||||||
|
availability: .all,
|
||||||
|
syncPolicy: .never
|
||||||
|
)
|
||||||
|
|
||||||
let name = "syncable_setting"
|
nonisolated static func syncableSettingKey(
|
||||||
let domain: StorageDomain = .userDefaults(suite: nil)
|
availability: PlatformAvailability = .all,
|
||||||
let security: SecurityPolicy = .none
|
syncPolicy: SyncPolicy = .never
|
||||||
let serializer: Serializer<String> = .json
|
) -> StorageKey {
|
||||||
let owner = "SampleApp"
|
StorageKey(
|
||||||
let description = "Stores a setting that can be synced to watch."
|
name: "syncable_setting",
|
||||||
let availability: PlatformAvailability
|
domain: .userDefaults(suite: nil),
|
||||||
let syncPolicy: SyncPolicy
|
security: .none,
|
||||||
|
serializer: .json,
|
||||||
init(availability: PlatformAvailability = .all, syncPolicy: SyncPolicy = .never) {
|
owner: "SampleApp",
|
||||||
self.availability = availability
|
description: "Stores a setting that can be synced to watch.",
|
||||||
self.syncPolicy = syncPolicy
|
availability: availability,
|
||||||
}
|
syncPolicy: syncPolicy
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,19 +1,17 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import LocalData
|
import LocalData
|
||||||
|
|
||||||
extension StorageKeys {
|
extension StorageKey where Value == Bool {
|
||||||
/// Watch-only setting for vibration.
|
/// Watch-only setting for vibration.
|
||||||
/// Grouped under Platform to highlight watch-only availability.
|
/// Grouped under Platform to highlight watch-only availability.
|
||||||
struct WatchVibrationKey: StorageKey {
|
nonisolated static let watchVibration = StorageKey(
|
||||||
typealias Value = Bool
|
name: "watch_vibration_enabled",
|
||||||
|
domain: .userDefaults(suite: nil),
|
||||||
let name = "watch_vibration_enabled"
|
security: .none,
|
||||||
let domain: StorageDomain = .userDefaults(suite: nil)
|
serializer: .json,
|
||||||
let security: SecurityPolicy = .none
|
owner: "SampleApp",
|
||||||
let serializer: Serializer<Bool> = .json
|
description: "Controls haptic feedback on watch-only experiences.",
|
||||||
let owner = "SampleApp"
|
availability: .watchOnly,
|
||||||
let description = "Controls haptic feedback on watch-only experiences."
|
syncPolicy: .never
|
||||||
let availability: PlatformAvailability = .watchOnly
|
)
|
||||||
let syncPolicy: SyncPolicy = .never
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,21 +1,19 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import LocalData
|
import LocalData
|
||||||
|
|
||||||
extension StorageKeys {
|
extension StorageKey where Value == String {
|
||||||
/// Stores the app version in standard UserDefaults.
|
/// Stores the app version in standard UserDefaults.
|
||||||
/// - Domain: UserDefaults (standard)
|
/// - Domain: UserDefaults (standard)
|
||||||
/// - Security: None
|
/// - Security: None
|
||||||
/// - Sync: Automatic for small data
|
/// - Sync: Automatic for small data
|
||||||
struct AppVersionKey: StorageKey {
|
nonisolated static let appVersion = StorageKey(
|
||||||
typealias Value = String
|
name: "last_app_version",
|
||||||
|
domain: .userDefaults(suite: nil),
|
||||||
let name = "last_app_version"
|
security: .none,
|
||||||
let domain: StorageDomain = .userDefaults(suite: nil)
|
serializer: .json,
|
||||||
let security: SecurityPolicy = .none
|
owner: "SampleApp",
|
||||||
let serializer: Serializer<String> = .json
|
description: "Tracks the last app version for migration and UI messaging.",
|
||||||
let owner = "SampleApp"
|
availability: .all,
|
||||||
let description = "Tracks the last app version for migration and UI messaging."
|
syncPolicy: .automaticSmall
|
||||||
let availability: PlatformAvailability = .all
|
)
|
||||||
let syncPolicy: SyncPolicy = .automaticSmall
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -64,11 +64,11 @@ struct AggregatingMigrationDemo: View {
|
|||||||
do {
|
do {
|
||||||
try await StorageRouter.shared.set(
|
try await StorageRouter.shared.set(
|
||||||
notificationsEnabled,
|
notificationsEnabled,
|
||||||
for: StorageKeys.LegacyNotificationSettingKey()
|
for: StorageKey.legacyNotificationSetting
|
||||||
)
|
)
|
||||||
try await StorageRouter.shared.set(
|
try await StorageRouter.shared.set(
|
||||||
theme,
|
theme,
|
||||||
for: StorageKeys.LegacyThemeSettingKey()
|
for: StorageKey.legacyThemeSetting
|
||||||
)
|
)
|
||||||
statusMessage = "✓ Saved legacy settings"
|
statusMessage = "✓ Saved legacy settings"
|
||||||
} catch {
|
} catch {
|
||||||
@ -83,7 +83,7 @@ struct AggregatingMigrationDemo: View {
|
|||||||
statusMessage = "Loading unified settings..."
|
statusMessage = "Loading unified settings..."
|
||||||
Task {
|
Task {
|
||||||
do {
|
do {
|
||||||
let value = try await StorageRouter.shared.get(StorageKeys.ModernUnifiedSettingsKey())
|
let value = try await StorageRouter.shared.get(StorageKey.modernUnifiedSettings)
|
||||||
modernValue = format(value)
|
modernValue = format(value)
|
||||||
statusMessage = "✓ Migration complete."
|
statusMessage = "✓ Migration complete."
|
||||||
} catch StorageError.notFound {
|
} catch StorageError.notFound {
|
||||||
|
|||||||
@ -63,7 +63,7 @@ struct ConditionalMigrationDemo: View {
|
|||||||
isLoading = true
|
isLoading = true
|
||||||
Task {
|
Task {
|
||||||
do {
|
do {
|
||||||
let key = StorageKeys.LegacyAppModeKey()
|
let key = StorageKey.legacyAppMode
|
||||||
try await StorageRouter.shared.set(legacyValue, for: key)
|
try await StorageRouter.shared.set(legacyValue, for: key)
|
||||||
statusMessage = "✓ Saved legacy app mode"
|
statusMessage = "✓ Saved legacy app mode"
|
||||||
} catch {
|
} catch {
|
||||||
@ -78,7 +78,7 @@ struct ConditionalMigrationDemo: View {
|
|||||||
statusMessage = "Loading modern mode..."
|
statusMessage = "Loading modern mode..."
|
||||||
Task {
|
Task {
|
||||||
do {
|
do {
|
||||||
let key = StorageKeys.ModernAppModeKey()
|
let key = StorageKey.modernAppMode
|
||||||
let value = try await StorageRouter.shared.get(key)
|
let value = try await StorageRouter.shared.get(key)
|
||||||
modernValue = value
|
modernValue = value
|
||||||
statusMessage = "✓ Conditional migration complete."
|
statusMessage = "✓ Conditional migration complete."
|
||||||
|
|||||||
@ -132,12 +132,12 @@ struct EncryptedStorageDemo: View {
|
|||||||
Task {
|
Task {
|
||||||
do {
|
do {
|
||||||
if useExternalKeyProvider {
|
if useExternalKeyProvider {
|
||||||
let key = StorageKeys.ExternalSessionLogsKey()
|
let key = StorageKey.externalSessionLogs
|
||||||
let logs = try await updatedLogs(for: key)
|
let logs = try await updatedLogs(for: key)
|
||||||
try await StorageRouter.shared.set(logs, for: key)
|
try await StorageRouter.shared.set(logs, for: key)
|
||||||
storedLogs = logs
|
storedLogs = logs
|
||||||
} else {
|
} else {
|
||||||
let key = StorageKeys.SessionLogsKey(iterations: iterations)
|
let key = StorageKey.sessionLogsKey(iterations: iterations)
|
||||||
let logs = try await updatedLogs(for: key)
|
let logs = try await updatedLogs(for: key)
|
||||||
try await StorageRouter.shared.set(logs, for: key)
|
try await StorageRouter.shared.set(logs, for: key)
|
||||||
storedLogs = logs
|
storedLogs = logs
|
||||||
@ -157,10 +157,10 @@ struct EncryptedStorageDemo: View {
|
|||||||
Task {
|
Task {
|
||||||
do {
|
do {
|
||||||
if useExternalKeyProvider {
|
if useExternalKeyProvider {
|
||||||
let key = StorageKeys.ExternalSessionLogsKey()
|
let key = StorageKey.externalSessionLogs
|
||||||
storedLogs = try await StorageRouter.shared.get(key)
|
storedLogs = try await StorageRouter.shared.get(key)
|
||||||
} else {
|
} else {
|
||||||
let key = StorageKeys.SessionLogsKey(iterations: iterations)
|
let key = StorageKey.sessionLogsKey(iterations: iterations)
|
||||||
storedLogs = try await StorageRouter.shared.get(key)
|
storedLogs = try await StorageRouter.shared.get(key)
|
||||||
}
|
}
|
||||||
statusMessage = "✓ Decrypted \(storedLogs.count) log entries"
|
statusMessage = "✓ Decrypted \(storedLogs.count) log entries"
|
||||||
@ -179,10 +179,10 @@ struct EncryptedStorageDemo: View {
|
|||||||
Task {
|
Task {
|
||||||
do {
|
do {
|
||||||
if useExternalKeyProvider {
|
if useExternalKeyProvider {
|
||||||
let key = StorageKeys.ExternalSessionLogsKey()
|
let key = StorageKey.externalSessionLogs
|
||||||
try await StorageRouter.shared.remove(key)
|
try await StorageRouter.shared.remove(key)
|
||||||
} else {
|
} else {
|
||||||
let key = StorageKeys.SessionLogsKey(iterations: iterations)
|
let key = StorageKey.sessionLogsKey(iterations: iterations)
|
||||||
try await StorageRouter.shared.remove(key)
|
try await StorageRouter.shared.remove(key)
|
||||||
}
|
}
|
||||||
storedLogs = []
|
storedLogs = []
|
||||||
@ -194,7 +194,7 @@ struct EncryptedStorageDemo: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updatedLogs<Key: StorageKey>(for key: Key) async throws -> [String] where Key.Value == [String] {
|
private func updatedLogs(for key: StorageKey<[String]>) async throws -> [String] {
|
||||||
var logs: [String]
|
var logs: [String]
|
||||||
do {
|
do {
|
||||||
logs = try await StorageRouter.shared.get(key)
|
logs = try await StorageRouter.shared.get(key)
|
||||||
|
|||||||
@ -172,7 +172,7 @@ struct FileSystemDemo: View {
|
|||||||
isLoading = true
|
isLoading = true
|
||||||
Task {
|
Task {
|
||||||
do {
|
do {
|
||||||
let key = StorageKeys.UserProfileFileKey(directory: selectedDirectory)
|
let key = StorageKey.userProfileFileKey(directory: selectedDirectory)
|
||||||
let profile = UserProfile(
|
let profile = UserProfile(
|
||||||
name: profileName,
|
name: profileName,
|
||||||
email: profileEmail,
|
email: profileEmail,
|
||||||
@ -192,7 +192,7 @@ struct FileSystemDemo: View {
|
|||||||
isLoading = true
|
isLoading = true
|
||||||
Task {
|
Task {
|
||||||
do {
|
do {
|
||||||
let key = StorageKeys.UserProfileFileKey(directory: selectedDirectory)
|
let key = StorageKey.userProfileFileKey(directory: selectedDirectory)
|
||||||
let profile = try await StorageRouter.shared.get(key)
|
let profile = try await StorageRouter.shared.get(key)
|
||||||
storedProfile = profile
|
storedProfile = profile
|
||||||
|
|
||||||
@ -216,7 +216,7 @@ struct FileSystemDemo: View {
|
|||||||
isLoading = true
|
isLoading = true
|
||||||
Task {
|
Task {
|
||||||
do {
|
do {
|
||||||
let key = StorageKeys.UserProfileFileKey(directory: selectedDirectory)
|
let key = StorageKey.userProfileFileKey(directory: selectedDirectory)
|
||||||
try await StorageRouter.shared.remove(key)
|
try await StorageRouter.shared.remove(key)
|
||||||
storedProfile = nil
|
storedProfile = nil
|
||||||
statusMessage = "✓ File deleted"
|
statusMessage = "✓ File deleted"
|
||||||
@ -231,7 +231,7 @@ struct FileSystemDemo: View {
|
|||||||
isLoading = true
|
isLoading = true
|
||||||
Task {
|
Task {
|
||||||
do {
|
do {
|
||||||
let key = StorageKeys.AppGroupUserProfileKey(directory: selectedDirectory)
|
let key = StorageKey.appGroupUserProfileKey(directory: selectedDirectory)
|
||||||
let profile = UserProfile(
|
let profile = UserProfile(
|
||||||
name: profileName,
|
name: profileName,
|
||||||
email: profileEmail,
|
email: profileEmail,
|
||||||
@ -251,7 +251,7 @@ struct FileSystemDemo: View {
|
|||||||
isLoading = true
|
isLoading = true
|
||||||
Task {
|
Task {
|
||||||
do {
|
do {
|
||||||
let key = StorageKeys.AppGroupUserProfileKey(directory: selectedDirectory)
|
let key = StorageKey.appGroupUserProfileKey(directory: selectedDirectory)
|
||||||
let profile = try await StorageRouter.shared.get(key)
|
let profile = try await StorageRouter.shared.get(key)
|
||||||
appGroupProfile = profile
|
appGroupProfile = profile
|
||||||
|
|
||||||
@ -275,7 +275,7 @@ struct FileSystemDemo: View {
|
|||||||
isLoading = true
|
isLoading = true
|
||||||
Task {
|
Task {
|
||||||
do {
|
do {
|
||||||
let key = StorageKeys.AppGroupUserProfileKey(directory: selectedDirectory)
|
let key = StorageKey.appGroupUserProfileKey(directory: selectedDirectory)
|
||||||
try await StorageRouter.shared.remove(key)
|
try await StorageRouter.shared.remove(key)
|
||||||
appGroupProfile = nil
|
appGroupProfile = nil
|
||||||
appGroupStatusMessage = "✓ App Group file deleted"
|
appGroupStatusMessage = "✓ App Group file deleted"
|
||||||
|
|||||||
@ -117,7 +117,7 @@ struct KeychainDemo: View {
|
|||||||
isLoading = true
|
isLoading = true
|
||||||
Task {
|
Task {
|
||||||
do {
|
do {
|
||||||
let key = StorageKeys.CredentialsKey(
|
let key = StorageKey.credentialsKey(
|
||||||
accessibility: selectedAccessibility,
|
accessibility: selectedAccessibility,
|
||||||
accessControl: selectedAccessControl
|
accessControl: selectedAccessControl
|
||||||
)
|
)
|
||||||
@ -135,7 +135,7 @@ struct KeychainDemo: View {
|
|||||||
isLoading = true
|
isLoading = true
|
||||||
Task {
|
Task {
|
||||||
do {
|
do {
|
||||||
let key = StorageKeys.CredentialsKey(
|
let key = StorageKey.credentialsKey(
|
||||||
accessibility: selectedAccessibility,
|
accessibility: selectedAccessibility,
|
||||||
accessControl: selectedAccessControl
|
accessControl: selectedAccessControl
|
||||||
)
|
)
|
||||||
@ -161,7 +161,7 @@ struct KeychainDemo: View {
|
|||||||
isLoading = true
|
isLoading = true
|
||||||
Task {
|
Task {
|
||||||
do {
|
do {
|
||||||
let key = StorageKeys.CredentialsKey(
|
let key = StorageKey.credentialsKey(
|
||||||
accessibility: selectedAccessibility,
|
accessibility: selectedAccessibility,
|
||||||
accessControl: selectedAccessControl
|
accessControl: selectedAccessControl
|
||||||
)
|
)
|
||||||
|
|||||||
@ -87,7 +87,7 @@ struct MigrationDemo: View {
|
|||||||
isLoading = true
|
isLoading = true
|
||||||
Task {
|
Task {
|
||||||
do {
|
do {
|
||||||
let key = StorageKeys.LegacyMigrationSourceKey()
|
let key = StorageKey.legacyMigrationSource
|
||||||
try await StorageRouter.shared.set(legacyValue, for: key)
|
try await StorageRouter.shared.set(legacyValue, for: key)
|
||||||
statusMessage = "✓ Saved '\(legacyValue)' to legacy UserDefaults"
|
statusMessage = "✓ Saved '\(legacyValue)' to legacy UserDefaults"
|
||||||
} catch {
|
} catch {
|
||||||
@ -102,7 +102,7 @@ struct MigrationDemo: View {
|
|||||||
statusMessage = "Retrieving from Modern..."
|
statusMessage = "Retrieving from Modern..."
|
||||||
Task {
|
Task {
|
||||||
do {
|
do {
|
||||||
let key = StorageKeys.ModernMigrationDestinationKey()
|
let key = StorageKey.modernMigrationDestination
|
||||||
let value = try await StorageRouter.shared.get(key)
|
let value = try await StorageRouter.shared.get(key)
|
||||||
modernValue = value
|
modernValue = value
|
||||||
statusMessage = "✓ Success! Data migrated from UserDefaults to Keychain."
|
statusMessage = "✓ Success! Data migrated from UserDefaults to Keychain."
|
||||||
@ -120,7 +120,7 @@ struct MigrationDemo: View {
|
|||||||
statusMessage = "Running manual migration..."
|
statusMessage = "Running manual migration..."
|
||||||
Task {
|
Task {
|
||||||
do {
|
do {
|
||||||
let key = StorageKeys.ModernMigrationDestinationKey()
|
let key = StorageKey.modernMigrationDestination
|
||||||
_ = try await StorageRouter.shared.forceMigration(for: key)
|
_ = try await StorageRouter.shared.forceMigration(for: key)
|
||||||
// Refresh modern value display
|
// Refresh modern value display
|
||||||
modernValue = try await StorageRouter.shared.get(key)
|
modernValue = try await StorageRouter.shared.get(key)
|
||||||
@ -136,7 +136,7 @@ struct MigrationDemo: View {
|
|||||||
isLoading = true
|
isLoading = true
|
||||||
Task {
|
Task {
|
||||||
do {
|
do {
|
||||||
let key = StorageKeys.LegacyMigrationSourceKey()
|
let key = StorageKey.legacyMigrationSource
|
||||||
let exists = try await StorageRouter.shared.exists(key)
|
let exists = try await StorageRouter.shared.exists(key)
|
||||||
statusMessage = exists ? "⚠️ Legacy data STILL EXISTS in UserDefaults!" : "✅ Legacy data was successfully DELETED."
|
statusMessage = exists ? "⚠️ Legacy data STILL EXISTS in UserDefaults!" : "✅ Legacy data was successfully DELETED."
|
||||||
} catch {
|
} catch {
|
||||||
|
|||||||
@ -114,7 +114,7 @@ struct PlatformSyncDemo: View {
|
|||||||
LabeledContent("Platform", value: selectedPlatform.displayName)
|
LabeledContent("Platform", value: selectedPlatform.displayName)
|
||||||
LabeledContent("Sync", value: selectedSync.displayName)
|
LabeledContent("Sync", value: selectedSync.displayName)
|
||||||
LabeledContent("Max Auto-Sync Size", value: "50 KB")
|
LabeledContent("Max Auto-Sync Size", value: "50 KB")
|
||||||
LabeledContent("Key Name", value: StorageKeys.SyncableSettingKey().name)
|
LabeledContent("Key Name", value: StorageKey.syncableSetting.name)
|
||||||
}
|
}
|
||||||
|
|
||||||
Section("Watch Notes") {
|
Section("Watch Notes") {
|
||||||
@ -141,7 +141,7 @@ struct PlatformSyncDemo: View {
|
|||||||
isLoading = true
|
isLoading = true
|
||||||
Task {
|
Task {
|
||||||
do {
|
do {
|
||||||
let key = StorageKeys.SyncableSettingKey(
|
let key = StorageKey.syncableSettingKey(
|
||||||
availability: selectedPlatform,
|
availability: selectedPlatform,
|
||||||
syncPolicy: selectedSync
|
syncPolicy: selectedSync
|
||||||
)
|
)
|
||||||
@ -161,7 +161,7 @@ struct PlatformSyncDemo: View {
|
|||||||
isLoading = true
|
isLoading = true
|
||||||
Task {
|
Task {
|
||||||
do {
|
do {
|
||||||
let key = StorageKeys.SyncableSettingKey(
|
let key = StorageKey.syncableSettingKey(
|
||||||
availability: selectedPlatform,
|
availability: selectedPlatform,
|
||||||
syncPolicy: selectedSync
|
syncPolicy: selectedSync
|
||||||
)
|
)
|
||||||
@ -184,7 +184,7 @@ struct PlatformSyncDemo: View {
|
|||||||
isLoading = true
|
isLoading = true
|
||||||
Task {
|
Task {
|
||||||
do {
|
do {
|
||||||
let key = StorageKeys.SyncableSettingKey(
|
let key = StorageKey.syncableSettingKey(
|
||||||
availability: selectedPlatform,
|
availability: selectedPlatform,
|
||||||
syncPolicy: selectedSync
|
syncPolicy: selectedSync
|
||||||
)
|
)
|
||||||
|
|||||||
@ -63,7 +63,7 @@ struct TransformingMigrationDemo: View {
|
|||||||
isLoading = true
|
isLoading = true
|
||||||
Task {
|
Task {
|
||||||
do {
|
do {
|
||||||
let key = StorageKeys.LegacyProfileNameKey()
|
let key = StorageKey.legacyProfileName
|
||||||
try await StorageRouter.shared.set(legacyValue, for: key)
|
try await StorageRouter.shared.set(legacyValue, for: key)
|
||||||
statusMessage = "✓ Saved legacy name \(legacyValue)"
|
statusMessage = "✓ Saved legacy name \(legacyValue)"
|
||||||
} catch {
|
} catch {
|
||||||
@ -78,7 +78,7 @@ struct TransformingMigrationDemo: View {
|
|||||||
statusMessage = "Loading modern profile..."
|
statusMessage = "Loading modern profile..."
|
||||||
Task {
|
Task {
|
||||||
do {
|
do {
|
||||||
let key = StorageKeys.ModernProfileNameKey()
|
let key = StorageKey.modernProfileName
|
||||||
let value = try await StorageRouter.shared.get(key)
|
let value = try await StorageRouter.shared.get(key)
|
||||||
modernValue = format(value)
|
modernValue = format(value)
|
||||||
statusMessage = "✓ Migration complete."
|
statusMessage = "✓ Migration complete."
|
||||||
|
|||||||
@ -140,7 +140,7 @@ struct UserDefaultsDemo: View {
|
|||||||
isLoading = true
|
isLoading = true
|
||||||
Task {
|
Task {
|
||||||
do {
|
do {
|
||||||
let key = StorageKeys.AppVersionKey()
|
let key = StorageKey.appVersion
|
||||||
try await StorageRouter.shared.set(inputText, for: key)
|
try await StorageRouter.shared.set(inputText, for: key)
|
||||||
statusMessage = "✓ Saved successfully"
|
statusMessage = "✓ Saved successfully"
|
||||||
} catch {
|
} catch {
|
||||||
@ -154,7 +154,7 @@ struct UserDefaultsDemo: View {
|
|||||||
isLoading = true
|
isLoading = true
|
||||||
Task {
|
Task {
|
||||||
do {
|
do {
|
||||||
let key = StorageKeys.AppVersionKey()
|
let key = StorageKey.appVersion
|
||||||
let value = try await StorageRouter.shared.get(key)
|
let value = try await StorageRouter.shared.get(key)
|
||||||
storedValue = value
|
storedValue = value
|
||||||
inputText = value // Sync to field
|
inputText = value // Sync to field
|
||||||
@ -173,7 +173,7 @@ struct UserDefaultsDemo: View {
|
|||||||
isLoading = true
|
isLoading = true
|
||||||
Task {
|
Task {
|
||||||
do {
|
do {
|
||||||
let key = StorageKeys.AppVersionKey()
|
let key = StorageKey.appVersion
|
||||||
try await StorageRouter.shared.remove(key)
|
try await StorageRouter.shared.remove(key)
|
||||||
storedValue = ""
|
storedValue = ""
|
||||||
statusMessage = "✓ Removed successfully"
|
statusMessage = "✓ Removed successfully"
|
||||||
@ -188,7 +188,7 @@ struct UserDefaultsDemo: View {
|
|||||||
isLoading = true
|
isLoading = true
|
||||||
Task {
|
Task {
|
||||||
do {
|
do {
|
||||||
let key = StorageKeys.AppGroupUserDefaultsKey()
|
let key = StorageKey.appGroupUserDefaults
|
||||||
try await StorageRouter.shared.set(inputText, for: key)
|
try await StorageRouter.shared.set(inputText, for: key)
|
||||||
statusMessage = "✓ App Group saved successfully"
|
statusMessage = "✓ App Group saved successfully"
|
||||||
} catch {
|
} catch {
|
||||||
@ -202,7 +202,7 @@ struct UserDefaultsDemo: View {
|
|||||||
isLoading = true
|
isLoading = true
|
||||||
Task {
|
Task {
|
||||||
do {
|
do {
|
||||||
let key = StorageKeys.AppGroupUserDefaultsKey()
|
let key = StorageKey.appGroupUserDefaults
|
||||||
let value = try await StorageRouter.shared.get(key)
|
let value = try await StorageRouter.shared.get(key)
|
||||||
appGroupStoredValue = value
|
appGroupStoredValue = value
|
||||||
inputText = value // Sync to field
|
inputText = value // Sync to field
|
||||||
@ -221,7 +221,7 @@ struct UserDefaultsDemo: View {
|
|||||||
isLoading = true
|
isLoading = true
|
||||||
Task {
|
Task {
|
||||||
do {
|
do {
|
||||||
let key = StorageKeys.AppGroupUserDefaultsKey()
|
let key = StorageKey.appGroupUserDefaults
|
||||||
try await StorageRouter.shared.remove(key)
|
try await StorageRouter.shared.remove(key)
|
||||||
appGroupStoredValue = ""
|
appGroupStoredValue = ""
|
||||||
statusMessage = "✓ App Group value removed"
|
statusMessage = "✓ App Group value removed"
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user