diff --git a/packages/core/src/session/Session.test.ts b/packages/core/src/session/Session.test.ts index 7a232e45..4bfde81b 100644 --- a/packages/core/src/session/Session.test.ts +++ b/packages/core/src/session/Session.test.ts @@ -1,5 +1,7 @@ -import { describe, it, expect, vi } from 'vitest'; +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { createSession, Session } from './Session.js'; +import { existsSync, unlinkSync, mkdirSync } from 'node:fs'; +import { dirname } from 'node:path'; describe('Session', () => { it('stores and retrieves values', () => { @@ -28,3 +30,51 @@ describe('Session', () => { expect(() => s.restore()).not.toThrow(); }); }); + +describe('Session persistence', () => { + const testPath = '/tmp/termui-test-session.json'; + + beforeEach(() => { + const dir = dirname(testPath); + if (!existsSync(dir)) mkdirSync(dir, { recursive: true }); + if (existsSync(testPath)) unlinkSync(testPath); + }); + + afterEach(() => { + if (existsSync(testPath)) unlinkSync(testPath); + }); + + it('persists data to disk and restores it across instances', () => { + const s1 = createSession({ storagePath: testPath }); + s1.set('theme', 'dark'); + s1.set('volume', 75); + s1.save(); + + const s2 = createSession({ storagePath: testPath }); + expect(s2.get('theme')).toBe('dark'); + expect(s2.get('volume')).toBe(75); + }); + + it('survives application restart cycle', () => { + const s1 = createSession({ storagePath: testPath }); + s1.set('user', { name: 'alice', role: 'admin' }); + s1.save(); + + const s2 = createSession({ storagePath: testPath }); + expect(s2.get('user')).toEqual({ name: 'alice', role: 'admin' }); + }); + + it('handles corrupt JSON gracefully', () => { + const dir = dirname(testPath); + if (!existsSync(dir)) mkdirSync(dir, { recursive: true }); + require('node:fs').writeFileSync(testPath, 'not-valid-json'); + + const s = createSession({ storagePath: testPath }); + expect(s.get('anything')).toBeUndefined(); + }); + + it('handles missing file gracefully on first run', () => { + const s = createSession({ storagePath: testPath }); + expect(s.get('anything')).toBeUndefined(); + }); +}); diff --git a/packages/core/src/session/Session.ts b/packages/core/src/session/Session.ts index ab82455d..776616a1 100644 --- a/packages/core/src/session/Session.ts +++ b/packages/core/src/session/Session.ts @@ -1,3 +1,7 @@ +import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs'; +import { dirname } from 'node:path'; +import { homedir } from 'node:os'; + // ───────────────────────────────────────────────────── // @termuijs/core — Session Management API // ───────────────────────────────────────────────────── @@ -8,6 +12,9 @@ export interface SessionOptions { /** Auto-save interval in milliseconds */ interval?: number; + + /** File path for persisting session data. Defaults to ~/.termui/session.json */ + storagePath?: string; } export interface SessionData { @@ -17,9 +24,11 @@ export interface SessionData { export class Session { private _data: SessionData = {}; private _timer?: ReturnType; + private _storagePath: string; constructor(private _options: SessionOptions = {}) { - // Start auto-save if enabled + this._storagePath = _options.storagePath ?? `${homedir()}/.termui/session.json`; + this.restore(); if (this._options.autoSave) { this._timer = setInterval(() => { this.save(); @@ -28,19 +37,35 @@ export class Session { } /** - * Save current session data. - * In future this can be connected - * to files, databases, or custom storage. + * Save current session data to a JSON file on disk. */ save(): void { - console.log('Session saved:', this._data); + try { + const dir = dirname(this._storagePath); + if (!existsSync(dir)) { + mkdirSync(dir, { recursive: true }); + } + writeFileSync(this._storagePath, JSON.stringify(this._data), 'utf-8'); + } catch { + // Silently ignore persistence failures (e.g. read-only filesystem) + } } /** - * Restore previous session. + * Restore previous session from disk. */ restore(): void { - console.log('Session restored'); + try { + if (existsSync(this._storagePath)) { + const raw = readFileSync(this._storagePath, 'utf-8'); + const parsed = JSON.parse(raw); + if (typeof parsed === 'object' && parsed !== null) { + this._data = parsed; + } + } + } catch { + // Silently ignore if file is corrupt or missing on first run + } } /**