migrate persistence from JSON to SwiftData
iOS CI / Build and Test SwiftUI App (push) Canceled after 0s
Build Unsigned iOS and macOS Apps / Build Unsigned iOS IPA (push) Canceled after 0s
Build Unsigned iOS and macOS Apps / Build macOS ZIP (push) Canceled after 0s

Models:
- Convert Conversation and ChatMessage to SwiftData @Model classes with
  a
  one-to-many relationship and cascade delete.
- Rename ChatMessage.id to clientID and drop Conversation.id in favor of
  jid
  (the string id would clash with PersistentIdentifier).
- Keep MessageReaction as a Codable value stored in an array attribute.

Persistence:
- Add ArchiveStore, a per-account ModelContainer/ModelContext that
  replaces
  the JSON ChatArchive with load/save/erase.
- Add ArchiveMetadataRecord for the durable MAM checkpoints, cursor,
  locally
  deleted message IDs and roster contact JIDs.
- Add LegacyArchiveImporter to import the old JSON snapshot once and
  delete
  the file afterwards, preserving existing history.

AppModel:
- Replace ChatArchive with ArchiveStore and persist via context.save();
  insert new models and delete the replaced object on upsert merges so
  SwiftData never keeps an orphaned duplicate.

Tests:
- Replace ChatArchiveTests with ArchiveStoreTests (store round-trip and
  legacy import) and drop the now-obsolete Codable round-trip
  assertions.
This commit is contained in:
wt committed 2026-08-18 22:58:18 +07:00
1 parent 0317277d46
commit cbb820a86e
30 files changed
+726 -665

No files matched your search

@@ -0,0 +1,37 @@
import Foundation
import SwiftData
/// Codable representation of a single durable MAM checkpoint, keyed by the
/// archive it belongs to (account or a specific MUC).
struct MAMCheckpointEntry: Codable, Equatable, Sendable {
let key: MAMArchiveKey
let checkpoint: MAMArchiveCheckpoint
}
/// Singleton per-account metadata that used to live in the JSON snapshot:
/// locally deleted message IDs, roster contact JIDs and MAM checkpoints.
@Model
final class ArchiveMetadata {
@Attribute(.unique) var accountJID: String
var locallyDeletedMessageIDs: [String]
var rosterContactJIDs: [String]
var lastSuccessfulMAMSync: Date?
var lastSuccessfulMAMCursor: String?
var mamCheckpoints: [MAMCheckpointEntry]
init(
accountJID: String,
locallyDeletedMessageIDs: [String] = [],
rosterContactJIDs: [String] = [],
lastSuccessfulMAMSync: Date? = nil,
lastSuccessfulMAMCursor: String? = nil,
mamCheckpoints: [MAMCheckpointEntry] = []
) {
self.accountJID = accountJID
self.locallyDeletedMessageIDs = locallyDeletedMessageIDs
self.rosterContactJIDs = rosterContactJIDs
self.lastSuccessfulMAMSync = lastSuccessfulMAMSync
self.lastSuccessfulMAMCursor = lastSuccessfulMAMCursor
self.mamCheckpoints = mamCheckpoints
}
}