P7/P8: add topology proxies and recovery reports

This commit is contained in:
2026-08-02 17:24:50 -04:00
parent 4cc349a589
commit 9c76f33f3d
11 changed files with 156 additions and 16 deletions

View File

@@ -1,11 +1,12 @@
import sqlite3InitModule, { type Database, type Sqlite3Static } from '@sqlite.org/sqlite-wasm'
import { PROJECT_SCHEMA_MIGRATIONS, PROJECT_SCHEMA_VERSION } from './projectSchema'
import type { DocumentObjectSnapshot, DocumentSnapshot, ModelTreeItem, ObjectPropertySnapshot, PersistenceCapabilities, ProjectResource } from './types'
import type { DocumentObjectSnapshot, DocumentSnapshot, ModelTreeItem, ObjectPropertySnapshot, PersistenceCapabilities, ProjectRecoveryReport, ProjectResource } from './types'
type PersistenceRequest =
| { id: number; type: 'initialize' }
| { id: number; type: 'save-document'; document: DocumentSnapshot }
| { id: number; type: 'load-document'; documentId: string }
| { id: number; type: 'recovery-report'; documentId: string }
| { id: number; type: 'put-resource'; bytes: ArrayBuffer; mediaType: string }
| { id: number; type: 'get-resource'; hash: string }
| { id: number; type: 'release-resource'; hash: string }
@@ -15,6 +16,7 @@ type PersistenceResponse =
| { id: number; ok: true; type: 'initialized'; capabilities: PersistenceCapabilities }
| { id: number; ok: true; type: 'saved'; documentId: string; documentVersion: number; persistedAt: number; mode: PersistenceCapabilities['mode'] }
| { id: number; ok: true; type: 'loaded'; document: DocumentSnapshot | null }
| { id: number; ok: true; type: 'recovery-report'; report: ProjectRecoveryReport }
| { id: number; ok: true; type: 'resource-put'; resource: ProjectResource }
| { id: number; ok: true; type: 'resource-get'; bytes: ArrayBuffer | null }
| { id: number; ok: true; type: 'resource-released' }
@@ -133,6 +135,17 @@ const loadDocument = (documentId: string): DocumentSnapshot | null => {
}
}
const recoveryReport = (documentId: string): ProjectRecoveryReport => {
if (!database) throw new Error('Persistence database is not initialized.')
const integrityRows = database.exec({ sql: 'PRAGMA integrity_check', rowMode: 'object', returnValue: 'resultRows' }) as Array<Record<string, string>>
const integrityValue = String(integrityRows[0]?.integrity_check ?? '')
const documents = database.exec({ sql: 'SELECT version, dirty FROM documents WHERE id = ?', bind: [documentId], rowMode: 'object', returnValue: 'resultRows' }) as Array<Record<string, string | number>>
const warnings: string[] = []
if (!documents[0]) warnings.push('No saved document exists for this ID.')
if (integrityValue !== 'ok') warnings.push(`SQLite integrity check returned: ${integrityValue || 'no result'}.`)
return { documentId, mode: capabilities.mode, schemaVersion: capabilities.schemaVersion, integrity: integrityValue === 'ok' ? 'ok' : 'failed', lastSavedVersion: documents[0] ? Number(documents[0].version) : null, dirtyAtLastSave: documents[0] ? Boolean(documents[0].dirty) : null, warnings }
}
const putResource = async (bytes: ArrayBuffer, mediaType: string): Promise<ProjectResource> => {
if (!database) throw new Error('Persistence database is not initialized.')
const hash = await hashBytes(bytes)
@@ -179,6 +192,7 @@ const handle = async (request: PersistenceRequest): Promise<PersistenceResponse>
await initialize()
if (request.type === 'save-document') { const result = saveDocument(request.document); return { id: request.id, ok: true, type: 'saved', ...result } }
if (request.type === 'load-document') return { id: request.id, ok: true, type: 'loaded', document: loadDocument(request.documentId) }
if (request.type === 'recovery-report') return { id: request.id, ok: true, type: 'recovery-report', report: recoveryReport(request.documentId) }
if (request.type === 'put-resource') return { id: request.id, ok: true, type: 'resource-put', resource: await putResource(request.bytes, request.mediaType) }
if (request.type === 'get-resource') return { id: request.id, ok: true, type: 'resource-get', bytes: await getResource(request.hash) }
if (request.type === 'release-resource') { await releaseResource(request.hash); return { id: request.id, ok: true, type: 'resource-released' } }