Files
goodbuddy/src/main/magic-notes/magic-note-storage.ts
T
mesalogo 13ff182858 fix: reduce redundant work during chat and note startup
Avoid hidden activity renders, no-op message saves and attachment scans when pinning conversations. Reuse note body inspection and skip unchanged attachment reference writes while preserving recovery and cleanup behavior.

Skip unnecessary Markdown normalization and memoize subagent lookups without delaying live rich text. Add storage operation counts and real Electron rendering equivalence benchmarks; full-render speedup remains unproven.

Validation: 5397 tests passed, 84 skipped; typecheck passed. Lint passed excluding two existing untracked sidebar probe files.

Release note: Reduce repeated rendering and disk work during chat, conversation pinning and note startup while preserving existing content and interactions.
2026-09-28 20:53:43 +08:00

170 lines
6.8 KiB
TypeScript

import { createHash, randomUUID } from 'node:crypto'
import fs, { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from 'node:fs'
import { dirname, join } from 'node:path'
import { tmpdir } from 'node:os'
import type { MagicNoteContent } from '../../shared/magic-notes-contracts'
export type MagicNoteStoredEntry = {
id: string
noteId: string
revision: number
updatedAt: string
content: MagicNoteContent
}
const extensions: Record<string, string> = {
'image/jpeg': 'jpg', 'image/png': 'png', 'image/gif': 'gif', 'image/webp': 'webp',
'application/pdf': 'pdf', 'text/plain': 'txt', 'video/mp4': 'mp4',
'video/webm': 'webm', 'video/ogg': 'ogv', 'video/quicktime': 'mov'
}
// Only media slots may contain storage references. Text and arbitrary canvas
// properties (including objects named "$asset") are ordinary user content.
function mapMediaFields(content: MagicNoteContent, transform: (value: unknown) => unknown): void {
const field = (record: Record<string, unknown>, key: string): void => {
if (record[key] !== undefined) record[key] = transform(record[key])
}
const embeds = (ops: Array<{ insert: unknown }>): void => {
for (const op of ops) {
if (!op.insert || typeof op.insert !== 'object') continue
const insert = op.insert as Record<string, unknown>
field(insert, 'image')
for (const key of ['localVideo', 'attachment']) {
const file = insert[key]
if (file && typeof file === 'object') field(file as Record<string, unknown>, 'dataUrl')
}
}
}
if (content.version === 1) {
embeds(content.ops)
return
}
for (const asset of content.assets) field(asset, 'dataUrl')
field(content, 'preview')
if (content.flow) embeds(content.flow.ops)
const objects = (items: Record<string, unknown>[]): void => {
for (const item of items) {
if (item.type === 'Image' || item.type === 'image' || item.canvasKind === 'image') field(item, 'src')
if (Array.isArray(item.objects)) objects(item.objects as Record<string, unknown>[])
}
}
for (const page of content.pages) objects(page.objects)
}
export class MagicNoteStorage {
readonly rootPath: string
constructor(databasePath: string) {
this.rootPath = databasePath === ':memory:'
? join(tmpdir(), `goodbuddy-memory-notes-${randomUUID()}`)
: join(dirname(databasePath), 'notes')
}
private atomicWrite(path: string, value: string | Buffer): void {
mkdirSync(dirname(path), { recursive: true })
const temporary = `${path}.${randomUUID()}.tmp`
try {
writeFileSync(temporary, value)
fs.renameSync(temporary, path)
} finally {
fs.rmSync(temporary, { force: true })
}
}
private entryPath(noteId: string, entryId: string): string {
return join(this.rootPath, noteId, 'entries', `${entryId}.json`)
}
inspect(noteId: string, entryId: string): { revision: number; referencedAssets?: Set<string> } {
const entry = JSON.parse(readFileSync(this.entryPath(noteId, entryId), 'utf8')) as MagicNoteStoredEntry
const referencedAssets = new Set<string>()
try {
mapMediaFields(entry.content, (value) => {
if (value && typeof value === 'object' && '$asset' in value) referencedAssets.add(String(value.$asset))
return value
})
} catch {
// Let reconciliation retry failed media inspection through its deferred-cleanup path.
return { revision: entry.revision }
}
return { revision: entry.revision, referencedAssets }
}
write(entry: MagicNoteStoredEntry): void {
const notePath = join(this.rootPath, entry.noteId)
const stored = JSON.parse(JSON.stringify(entry)) as MagicNoteStoredEntry
mapMediaFields(stored.content, (value) => {
if (typeof value !== 'string') return value
const match = /^data:([^;]+);base64,([A-Za-z0-9+/]*={0,2})$/.exec(value)
if (!match) return value
const mimeType = match[1]!
const bytes = Buffer.from(match[2]!, 'base64')
const name = `${createHash('sha256').update(bytes).digest('hex')}.${extensions[mimeType] ?? 'bin'}`
const path = join(notePath, 'assets', name)
if (!existsSync(path)) this.atomicWrite(path, bytes)
return { $asset: `assets/${name}`, mimeType }
})
this.atomicWrite(this.entryPath(entry.noteId, entry.id), JSON.stringify(stored))
}
read(noteId: string, entryId: string): MagicNoteStoredEntry {
const entry = JSON.parse(readFileSync(this.entryPath(noteId, entryId), 'utf8')) as MagicNoteStoredEntry
mapMediaFields(entry.content, (value) => {
if (!value || typeof value !== 'object' || !('$asset' in value)) return value
const reference = value as { $asset: string; mimeType: string }
if (!/^assets\/[a-f0-9]{64}\.[a-z0-9]+$/.test(reference.$asset)) throw new Error('Invalid note asset reference')
return `data:${reference.mimeType};base64,${readFileSync(join(this.rootPath, noteId, reference.$asset)).toString('base64')}`
})
if (entry.id !== entryId || entry.noteId !== noteId) throw new Error('Note entry identity mismatch')
return entry
}
reconcile(noteId: string, entryIds: string[], inspectedAssets?: ReadonlySet<string>): void {
const notePath = join(this.rootPath, noteId)
const entriesPath = join(notePath, 'entries')
const retained = new Set(entryIds.map((id) => `${id}.json`))
const referenced = new Set<string>()
// Repair can supply references only after inspecting every retained body.
// Otherwise read them all before deleting anything, including shared assets.
if (!inspectedAssets) {
for (const name of retained) {
const entry = JSON.parse(readFileSync(join(entriesPath, name), 'utf8')) as MagicNoteStoredEntry
mapMediaFields(entry.content, (value) => {
if (value && typeof value === 'object' && '$asset' in value) referenced.add(String(value.$asset))
return value
})
}
}
this.atomicWrite(join(notePath, 'note.json'), JSON.stringify({ id: noteId, entries: entryIds }))
if (existsSync(entriesPath)) {
for (const name of readdirSync(entriesPath)) {
if (!retained.has(name)) {
fs.rmSync(join(entriesPath, name), { force: true })
continue
}
}
}
const assetsPath = join(notePath, 'assets')
if (existsSync(assetsPath)) {
for (const name of readdirSync(assetsPath)) {
if (!(inspectedAssets ?? referenced).has(`assets/${name}`)) fs.rmSync(join(assetsPath, name), { force: true })
}
}
}
deleteNote(noteId: string): void {
fs.rmSync(join(this.rootPath, noteId), { recursive: true, force: true })
}
reconcileNotes(noteIds: Set<string>): void {
if (!existsSync(this.rootPath)) return
for (const name of readdirSync(this.rootPath)) {
if (/^[a-f0-9-]{36}$/.test(name) && !noteIds.has(name)) this.deleteNote(name)
}
}
clear(): void {
fs.rmSync(this.rootPath, { recursive: true, force: true })
}
}