@vfarcic/dot-ai
Version:
AI-powered development productivity platform that enhances software development workflows through intelligent automation and AI-driven assistance
200 lines (199 loc) • 6.73 kB
JavaScript
;
/**
* Generic Session Manager
*
* Reusable file-based session management for MCP tools
* Provides CRUD operations with persistent storage
*
* Usage:
* const manager = new GenericSessionManager<MySessionData>('myprefix', args);
* const session = manager.createSession({ myData: 'value' });
*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.GenericSessionManager = void 0;
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const crypto_1 = require("crypto");
const session_utils_1 = require("./session-utils");
/**
* Generic session manager with file-based storage
*/
class GenericSessionManager {
prefix;
sessionDir;
sessionsPath;
/**
* Create a new session manager
* @param prefix - Prefix for session IDs and directory (e.g., 'proj', 'pattern', 'test')
*/
constructor(prefix) {
this.prefix = prefix;
this.sessionDir = (0, session_utils_1.getAndValidateSessionDirectory)(true);
this.sessionsPath = path.join(this.sessionDir, `${prefix}-sessions`);
// Create sessions directory if it doesn't exist
if (!fs.existsSync(this.sessionsPath)) {
fs.mkdirSync(this.sessionsPath, { recursive: true });
}
}
/**
* Create a new session
* Pattern: {prefix}-{timestamp}-{uuid}
*/
createSession(initialData = {}) {
const sessionId = `${this.prefix}-${Date.now()}-${(0, crypto_1.randomUUID)().substring(0, 8)}`;
const now = new Date().toISOString();
const session = {
sessionId,
createdAt: now,
updatedAt: now,
data: initialData,
};
this.saveSession(session);
return session;
}
/**
* Get an existing session
*/
getSession(sessionId) {
try {
const sessionFile = path.join(this.sessionsPath, `${sessionId}.json`);
if (!fs.existsSync(sessionFile)) {
return null;
}
const sessionData = fs.readFileSync(sessionFile, 'utf8');
return JSON.parse(sessionData);
}
catch (error) {
console.error(`Failed to load session ${sessionId}:`, error);
return null;
}
}
/**
* Update session data (merges with existing data)
*/
updateSession(sessionId, newData) {
const session = this.getSession(sessionId);
if (!session) {
return null;
}
session.data = { ...session.data, ...newData };
session.updatedAt = new Date().toISOString();
this.saveSession(session);
return session;
}
/**
* Replace session data entirely
*/
replaceSession(sessionId, newData) {
const session = this.getSession(sessionId);
if (!session) {
return null;
}
session.data = newData;
session.updatedAt = new Date().toISOString();
this.saveSession(session);
return session;
}
/**
* Delete a session
*/
deleteSession(sessionId) {
try {
const sessionFile = path.join(this.sessionsPath, `${sessionId}.json`);
if (!fs.existsSync(sessionFile)) {
return false;
}
fs.unlinkSync(sessionFile);
return true;
}
catch (error) {
console.error(`Failed to delete session ${sessionId}:`, error);
return false;
}
}
/**
* List all sessions (returns session IDs)
*/
listSessions() {
try {
if (!fs.existsSync(this.sessionsPath)) {
return [];
}
return fs
.readdirSync(this.sessionsPath)
.filter((file) => file.endsWith('.json'))
.map((file) => file.replace('.json', ''));
}
catch (error) {
console.error('Failed to list sessions:', error);
return [];
}
}
/**
* Clear all sessions (useful for testing)
*/
clearAllSessions() {
try {
if (!fs.existsSync(this.sessionsPath)) {
return;
}
const sessions = fs.readdirSync(this.sessionsPath);
for (const file of sessions) {
if (file.endsWith('.json')) {
fs.unlinkSync(path.join(this.sessionsPath, file));
}
}
}
catch (error) {
console.error('Failed to clear sessions:', error);
}
}
/**
* Save session to file
*
* Note: Uses a custom replacer to convert undefined values to null.
* This is critical because JSON.stringify drops undefined values entirely,
* which would cause data loss in toolCallsExecuted arrays where tool
* outputs may have undefined fields. (PRD #320 Milestone 2.5)
*/
saveSession(session) {
const sessionFile = path.join(this.sessionsPath, `${session.sessionId}.json`);
// Convert undefined to null to preserve structure during JSON serialization
const replacer = (_key, value) => value === undefined ? null : value;
fs.writeFileSync(sessionFile, JSON.stringify(session, replacer, 2), 'utf8');
}
}
exports.GenericSessionManager = GenericSessionManager;