Files
notes/Notes/Model/NoteMapper.swift
T
rzen b2f16e8600 Add Projects: third tab, swipe-to-file, per-project note capture
Notes gain an optional project (schema v4, additive). Projects derive
from note values like tags — no separate entity. Tab naming
(Projects/Categories) is a Settings choice applied across the UI.

Claude-Session: https://claude.ai/code/session_014esDWi42URLEC6Cj17hGQ3
2026-07-16 18:24:11 -04:00

64 lines
2.5 KiB
Swift

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<NoteEntity>(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<NoteEntity>(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.project = note.payload.project
entity.createdAt = note.meta.createdAt
entity.modifiedAt = note.meta.modifiedAt
entity.jsonRelativePath = relativePath
entity.context = note.payload.context
entity.audio = note.payload.audio
}
/// 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,
audio: entity.audio,
project: entity.project
)
)
}
}