Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 51 additions & 1 deletion packages/core/src/session/Session.test.ts
Original file line number Diff line number Diff line change
@@ -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', () => {
Expand Down Expand Up @@ -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);
});
Comment thread
ionfwsrijan marked this conversation as resolved.

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');
Comment thread
ionfwsrijan marked this conversation as resolved.

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();
});
});
39 changes: 32 additions & 7 deletions packages/core/src/session/Session.ts
Original file line number Diff line number Diff line change
@@ -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
// ─────────────────────────────────────────────────────
Expand All @@ -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 {
Expand All @@ -17,9 +24,11 @@ export interface SessionData {
export class Session {
private _data: SessionData = {};
private _timer?: ReturnType<typeof setInterval>;
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();
Expand All @@ -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 {
Comment thread
ionfwsrijan marked this conversation as resolved.
// 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;
Comment thread
ionfwsrijan marked this conversation as resolved.
}
}
} catch {
// Silently ignore if file is corrupt or missing on first run
}
}

/**
Expand Down
Loading