import type { AppPersistencePort } from "../core/app-service-ports"; import type { PluginStateRecord } from "./plugin-state-store"; import type { SessionSnapshotRecord } from "./session-store"; import { MemoryResourceStore } from "./memory-resource-store"; import { BROWSER_STORAGE_KEYS, SafeJsonStorage, type StorageLike } from "./json-storage"; type PluginState = Record>; type Sessions = Record; function isRecord(value: unknown): value is Record { return !!value && typeof value === "object" && !Array.isArray(value); } export class JsonPersistence implements AppPersistencePort { readonly resources = new MemoryResourceStore(); private readonly pluginStateData: SafeJsonStorage; private readonly sessionData: SafeJsonStorage; constructor(storage?: StorageLike) { this.pluginStateData = new SafeJsonStorage(storage, BROWSER_STORAGE_KEYS.pluginState, {}, (value): value is PluginState => isRecord(value)); this.sessionData = new SafeJsonStorage(storage, BROWSER_STORAGE_KEYS.session, {}, (value): value is Sessions => isRecord(value)); } readonly pluginState = { get: (pluginId: string, key: string, schemaVersion = 1): PluginStateRecord | null => { const record = this.pluginStateData.get()[pluginId]?.[key]; if (!isRecord(record) || record.schemaVersion !== schemaVersion || !("value" in record)) return null; return record as unknown as PluginStateRecord; }, set: (pluginId: string, key: string, value: unknown, schemaVersion = 1): void => { const state = this.pluginStateData.get(); this.pluginStateData.set({ ...state, [pluginId]: { ...state[pluginId], [key]: { value, schemaVersion, updatedAt: Date.now() }, }, }); }, delete: (pluginId: string, key: string): void => { const state = this.pluginStateData.get(); const plugin = { ...state[pluginId] }; delete plugin[key]; this.pluginStateData.set({ ...state, [pluginId]: plugin }); }, keys: (pluginId: string): string[] => Object.keys(this.pluginStateData.get()[pluginId] ?? {}), clear: (pluginId: string): void => { const state = { ...this.pluginStateData.get() }; delete state[pluginId]; this.pluginStateData.set(state); }, }; readonly sessions = { get: (sessionId = "app", schemaVersion = 1): SessionSnapshotRecord | null => { const record = this.sessionData.get()[sessionId]; if (!isRecord(record) || record.schemaVersion !== schemaVersion || !("value" in record)) return null; return record as unknown as SessionSnapshotRecord; }, set: (sessionId: string, value: unknown, schemaVersion = 1): void => { this.sessionData.set({ ...this.sessionData.get(), [sessionId]: { sessionId, value, schemaVersion, updatedAt: Date.now() }, }); }, delete: (sessionId: string): void => { const state = { ...this.sessionData.get() }; delete state[sessionId]; this.sessionData.set(state); }, }; close(): void {} }