import Foundation import IndieSync import SwiftData /// Document ↔ cache-entity mapping. Nothing else in the app knows both /// shapes. `upsert` is used only by the metadata observer and cache /// rebuilds — app code never writes the cache directly. enum NoteMapper { static func fetch(id: String, in context: ModelContext) -> NoteEntity? { var descriptor = FetchDescriptor(predicate: #Predicate { $0.id == id }) descriptor.fetchLimit = 1 return try? context.fetch(descriptor).first } static func fetch(jsonRelativePath path: String, in context: ModelContext) -> [NoteEntity] { let descriptor = FetchDescriptor(predicate: #Predicate { $0.jsonRelativePath == path }) return (try? context.fetch(descriptor)) ?? [] } static func upsert(_ note: Note, relativePath: String, into context: ModelContext) { let idString = note.meta.id.stringValue let entity: NoteEntity if let existing = fetch(id: idString, in: context) { entity = existing } else { entity = NoteEntity(id: idString, jsonRelativePath: relativePath) context.insert(entity) } // Always overwrite, not just on insert — the document just read is at // least as fresh as whatever the cache last held. entity.text = note.payload.text entity.sourceRaw = note.payload.source.rawValue entity.tags = note.payload.tags entity.createdAt = note.meta.createdAt entity.modifiedAt = note.meta.modifiedAt entity.jsonRelativePath = relativePath entity.context = note.payload.context } /// Near-lossless reconstruction from the cache, used only as the offline /// fallback when the authoritative file can't be read. static func note(from entity: NoteEntity) -> Note? { guard let id = ULID(string: entity.id) else { return nil } return Note( meta: Note.Meta( id: id, schemaVersion: Note.currentSchemaVersion, createdAt: entity.createdAt, modifiedAt: entity.modifiedAt ), payload: Note.Payload( text: entity.text, source: entity.source, tags: entity.tags, context: entity.context ) ) } }