MCP GitHub Issue Server
by sammcj
/**
* WAL file system operations and integrity checks
*/
import { Logger } from '../../../logging/index.js';
import { ErrorCodes, createError } from '../../../errors/index.js';
import { WALFileInfo } from './types.js';
import { promises as fs } from 'fs';
import { dirname } from 'path';
import { getWALPaths } from './wal-paths.js';
export class FileHandler {
private readonly logger: Logger;
private readonly PAGE_SIZE = 4096; // Standard SQLite page size
constructor(private readonly dbPath: string) {
this.logger = Logger.getInstance().child({
component: 'FileHandler',
context: { dbPath },
});
}
/**
* Initialize WAL directory
*/
async initializeDirectory(): Promise<void> {
const dir = dirname(this.dbPath);
try {
// Ensure directory exists with proper permissions
await fs.mkdir(dir, { recursive: true, mode: 0o755 });
await fs.access(dir, fs.constants.R_OK | fs.constants.W_OK);
const stats = await fs.stat(dir);
this.logger.info('Database directory ready', {
dir,
mode: stats.mode,
context: {
operation: 'initializeDirectory',
timestamp: Date.now(),
},
});
} catch (error) {
throw createError(
ErrorCodes.STORAGE_INIT,
'Failed to prepare database directory',
'initializeDirectory',
error instanceof Error ? error.message : String(error),
{
originalError:
error instanceof Error
? {
name: error.name,
message: error.message,
stack: error.stack,
}
: undefined,
dir,
}
);
}
}
/**
* Get WAL file information
*/
async getWALInfo(): Promise<WALFileInfo> {
const { walPath, shmPath } = getWALPaths(this.dbPath);
try {
const walStats = await fs.stat(walPath);
return {
walPath,
shmPath,
walSize: walStats.size,
isPageAligned: walStats.size % this.PAGE_SIZE === 0,
lastModified: walStats.mtimeMs,
};
} catch (error) {
if ((error as NodeJS.ErrnoException).code !== 'ENOENT') {
// Log unexpected errors but not missing file errors
this.logger.warn('WAL file access error', {
error,
context: {
operation: 'getWALInfo',
timestamp: Date.now(),
},
});
} else {
this.logger.debug('WAL file not found - normal for initial state', {
context: {
operation: 'getWALInfo',
timestamp: Date.now(),
},
});
}
return {
walPath,
shmPath,
walSize: 0,
isPageAligned: true,
lastModified: 0,
};
}
}
/**
* Verify WAL file integrity
*/
async verifyIntegrity(): Promise<boolean> {
const verifyStart = Date.now();
try {
// Get WAL info
const info = await this.getWALInfo();
// Check if WAL and SHM files exist
await Promise.all([
fs.access(info.walPath).catch(() => {}),
fs.access(info.shmPath).catch(() => {}),
]);
// Basic integrity checks
if (info.walSize === 0) {
this.logger.warn('WAL file is empty', {
walPath: info.walPath,
context: {
operation: 'verifyIntegrity',
timestamp: Date.now(),
},
});
return false;
}
if (!info.isPageAligned) {
this.logger.warn('WAL file size is not page-aligned', {
size: info.walSize,
pageSize: this.PAGE_SIZE,
walPath: info.walPath,
context: {
operation: 'verifyIntegrity',
timestamp: Date.now(),
},
});
return false;
}
this.logger.debug('WAL integrity verified', {
walSize: info.walSize,
duration: Date.now() - verifyStart,
context: {
operation: 'verifyIntegrity',
timestamp: Date.now(),
},
});
return true;
} catch (error) {
this.logger.warn('WAL integrity check failed', {
error,
duration: Date.now() - verifyStart,
context: {
operation: 'verifyIntegrity',
timestamp: Date.now(),
},
});
return false;
}
}
/**
* Clean up WAL files
*/
async cleanup(): Promise<void> {
try {
const info = await this.getWALInfo();
// Try to remove WAL and SHM files
await Promise.all([
fs.unlink(info.walPath).catch(() => {}),
fs.unlink(info.shmPath).catch(() => {}),
]);
this.logger.debug('WAL files cleaned up', {
context: {
operation: 'cleanup',
timestamp: Date.now(),
},
});
} catch (error) {
this.logger.warn('Failed to clean up WAL files', {
error,
context: {
operation: 'cleanup',
timestamp: Date.now(),
},
});
}
}
/**
* Check if WAL mode is supported
*/
async checkWALSupport(): Promise<boolean> {
try {
// Try to create and write to WAL file
const info = await this.getWALInfo();
await fs.writeFile(info.walPath, Buffer.alloc(this.PAGE_SIZE));
await fs.unlink(info.walPath);
return true;
} catch (error) {
this.logger.warn('WAL mode not supported', {
error,
context: {
operation: 'checkWALSupport',
timestamp: Date.now(),
},
});
return false;
}
}
/**
* Get WAL directory status
*/
async getDirectoryStatus() {
const dir = dirname(this.dbPath);
try {
const stats = await fs.stat(dir);
return {
exists: true,
isWritable: true,
mode: stats.mode,
size: stats.size,
lastModified: stats.mtimeMs,
};
} catch (error) {
return {
exists: false,
isWritable: false,
mode: 0,
size: 0,
lastModified: 0,
};
}
}
}