@shirokuma-library/mcp-knowledge-base
Version:
Shirokuma MCP Server for comprehensive knowledge management including issues, plans, documents, and work sessions. All stored data is structured for AI processing, not human readability.
208 lines (207 loc) • 7.32 kB
JavaScript
import { createLogger } from '../utils/logger.js';
export class RepositoryBase {
}
export class BaseRepository {
db;
tableName;
logger;
constructor(db, tableName, loggerName) {
this.db = db;
this.tableName = tableName;
this.logger = createLogger(loggerName);
if (this.isMCPEnvironment()) {
const noop = () => this.logger;
this.logger.debug = noop;
this.logger.info = noop;
this.logger.warn = noop;
this.logger.error = noop;
}
}
isMCPEnvironment() {
if (process.env.NODE_ENV === 'test' || process.env.MCP_MODE === 'false') {
return false;
}
return process.argv.some(arg => arg.includes('server.js')) ||
process.env.MCP_MODE === 'true' ||
process.env.NODE_ENV === 'production';
}
async getNextId(type) {
const result = await this.db.runAsync('UPDATE sequences SET current_value = current_value + 1 WHERE type = ?', [type]);
if (result.changes === 0) {
throw new Error(`Sequence type '${type}' not found`);
}
const row = await this.db.getAsync('SELECT current_value FROM sequences WHERE type = ?', [type]);
return Number(row?.current_value || 0);
}
async findById(id) {
try {
const query = `SELECT * FROM ${this.tableName} WHERE id = ?`;
const row = await this.db.getAsync(query, [id]);
if (!row) {
return null;
}
return this.mapRowToEntity(row);
}
catch (error) {
this.logger.error(`Failed to find by ID in ${this.tableName}`, { error, id });
throw error;
}
}
async findAll(options) {
try {
let query = `SELECT * FROM ${this.tableName}`;
const params = [];
if (options?.where) {
const conditions = Object.entries(options.where)
.filter(([, value]) => value !== undefined)
.map(([key, value]) => {
params.push(value);
return `${key} = ?`;
})
.join(' AND ');
if (conditions) {
query += ` WHERE ${conditions}`;
}
}
if (options?.orderBy) {
query += ` ORDER BY ${String(options.orderBy)} ${options.order || 'ASC'}`;
}
if (options?.limit) {
query += ` LIMIT ${options.limit}`;
if (options.offset) {
query += ` OFFSET ${options.offset}`;
}
}
const rows = await this.db.allAsync(query, params);
return rows.map(row => this.mapRowToEntity(row));
}
catch (error) {
this.logger.error(`Failed to find all in ${this.tableName}`, { error, options });
throw error;
}
}
async create(data) {
try {
const row = this.mapEntityToRow(data);
const fields = Object.keys(row);
const values = Object.values(row);
const placeholders = fields.map(() => '?').join(', ');
const query = `INSERT INTO ${this.tableName} (${fields.join(', ')}) VALUES (${placeholders})`;
const result = await this.db.runAsync(query, values);
return await this.findById(result.lastID);
}
catch (error) {
this.logger.error(`Failed to create in ${this.tableName}`, { error, data });
throw error;
}
}
async executeQuery(query, params = []) {
try {
return await this.db.allAsync(query, params);
}
catch (error) {
this.logger.error('Query execution failed', { error, query, params });
throw error;
}
}
async executeRun(query, params = []) {
try {
return await this.db.runAsync(query, params);
}
catch (error) {
this.logger.error('Run execution failed', { error, query, params });
throw error;
}
}
async updateById(id, data) {
try {
const exists = await this.exists(id);
if (!exists) {
return null;
}
const updateData = { ...data };
if ('id' in updateData) {
delete updateData.id;
}
const row = this.mapEntityToRow(updateData);
const updates = Object.entries(row)
.filter(([key]) => key !== 'id')
.map(([key]) => `${key} = ?`)
.join(', ');
const values = Object.entries(row)
.filter(([key]) => key !== 'id')
.map(([, value]) => value);
values.push(id);
const query = `UPDATE ${this.tableName} SET ${updates} WHERE id = ?`;
await this.db.runAsync(query, values);
return await this.findById(id);
}
catch (error) {
this.logger.error(`Failed to update in ${this.tableName}`, { error, id, data });
throw error;
}
}
async deleteById(id) {
try {
const query = `DELETE FROM ${this.tableName} WHERE id = ?`;
const result = await this.db.runAsync(query, [id]);
return result.changes > 0;
}
catch (error) {
this.logger.error(`Failed to delete from ${this.tableName}`, { error, id });
throw error;
}
}
async count(where) {
try {
let query = `SELECT COUNT(*) as count FROM ${this.tableName}`;
const params = [];
if (where) {
const conditions = Object.entries(where)
.map(([key, value]) => {
params.push(value);
return `${key} = ?`;
})
.join(' AND ');
if (conditions) {
query += ` WHERE ${conditions}`;
}
}
const result = await this.db.getAsync(query, params);
return Number(result?.count) || 0;
}
catch (error) {
this.logger.error(`Failed to count in ${this.tableName}`, { error, where });
throw error;
}
}
async exists(id) {
try {
const query = `SELECT 1 FROM ${this.tableName} WHERE id = ? LIMIT 1`;
const row = await this.db.getAsync(query, [id]);
return row !== undefined;
}
catch (error) {
this.logger.error(`Failed to check existence in ${this.tableName}`, { error, id });
throw error;
}
}
async transaction(callback) {
try {
await this.db.runAsync('BEGIN');
try {
const result = await callback(this.db);
await this.db.runAsync('COMMIT');
return result;
}
catch (error) {
await this.db.runAsync('ROLLBACK');
throw error;
}
}
catch (error) {
this.logger.error('Transaction failed', { error });
throw error;
}
}
}