draft-storage.ts (2222B)
1 // One local recovery slot, never executable state or an automatic run request. 2 // Browser limits are intentionally NOT persisted: recovery starts in Standard. 3 export const DRAFT_KEY = 'arboricx.local-draft.v1'; 4 export interface SavedDraft { 5 schema: 1; 6 source: string; 7 name: string; 8 arguments: string; 9 output: string; 10 mode: 'pure' | 'io'; 11 demo?: { hash: string; environment: string }; 12 } 13 export function savedDraft(raw: unknown): SavedDraft { 14 if (!raw || typeof raw !== 'object' || Array.isArray(raw)) throw new Error('Invalid saved draft'); 15 const v = raw as Record<string, unknown>; 16 for (const [key, max] of [['source', 32768], ['name', 256], ['arguments', 16000], ['output', 32]] as const) 17 if (typeof v[key] !== 'string' || v[key].length > max) throw new Error('Invalid saved draft ' + key); 18 if (v['schema'] !== 1 || (v['mode'] !== 'pure' && v['mode'] !== 'io')) throw new Error('Invalid saved draft version/mode'); 19 let demo: SavedDraft['demo']; 20 if (v['demo'] !== undefined) { 21 const d = v['demo'] as Record<string, unknown>; 22 if (!d || typeof d !== 'object' || typeof d['hash'] !== 'string' || !/^[0-9a-f]{64}$/u.test(d['hash']) || typeof d['environment'] !== 'string' || !/^[A-Za-z0-9_-]{1,128}$/u.test(d['environment'])) throw new Error('Invalid saved demo context'); 23 demo = { hash: d['hash'], environment: d['environment'] }; 24 } 25 return { schema: 1, source: v['source'] as string, name: v['name'] as string, arguments: v['arguments'] as string, 26 output: v['output'] as string, mode: v['mode'], ...(demo ? { demo } : {}) }; 27 } 28 export function readDraft(storage: Pick<Storage, 'getItem'>): SavedDraft | undefined { 29 const text = storage.getItem(DRAFT_KEY); 30 if (text === null) return undefined; 31 if (text.length > 100000) throw new Error('Saved draft too large'); 32 return savedDraft(JSON.parse(text) as unknown); 33 } 34 export function writeDraft(storage: Pick<Storage, 'setItem' | 'getItem'>, draft: SavedDraft): void { 35 const text = JSON.stringify(savedDraft(draft)); 36 if (text.length > 100000) throw new Error('Saved draft too large'); 37 storage.setItem(DRAFT_KEY, text); 38 if (storage.getItem(DRAFT_KEY) !== text) throw new Error('Draft storage verification failed'); 39 }