- cap contexts to 10 per account - align MCP and onboarding context guidance - keep contexts optional unless obviously relevant
142 lines
4.0 KiB
JavaScript
142 lines
4.0 KiB
JavaScript
'use strict';
|
|
|
|
const { getDb } = require('./sqlite-client');
|
|
const MAX_CONTEXTS_PER_ACCOUNT = 10;
|
|
|
|
function mapContext(row) {
|
|
if (!row) return null;
|
|
return {
|
|
id: row.id,
|
|
name: row.name,
|
|
description: row.description ?? null,
|
|
icon: row.icon ?? null,
|
|
count: Number(row.count ?? 0),
|
|
};
|
|
}
|
|
|
|
function listContexts() {
|
|
const db = getDb();
|
|
const rows = db.prepare(`
|
|
SELECT c.id, c.name, c.description, c.icon, COUNT(n.id) AS count
|
|
FROM contexts c
|
|
LEFT JOIN nodes n ON n.context_id = c.id
|
|
GROUP BY c.id
|
|
ORDER BY c.name COLLATE NOCASE ASC
|
|
`).all();
|
|
return rows.map(mapContext);
|
|
}
|
|
|
|
function getContextById(id) {
|
|
const db = getDb();
|
|
const row = db.prepare(`
|
|
SELECT c.id, c.name, c.description, c.icon, COUNT(n.id) AS count
|
|
FROM contexts c
|
|
LEFT JOIN nodes n ON n.context_id = c.id
|
|
WHERE c.id = ?
|
|
GROUP BY c.id
|
|
`).get(id);
|
|
return mapContext(row);
|
|
}
|
|
|
|
function getContextByName(name) {
|
|
const trimmed = typeof name === 'string' ? name.trim() : '';
|
|
if (!trimmed) return null;
|
|
const db = getDb();
|
|
const row = db.prepare(`
|
|
SELECT c.id, c.name, c.description, c.icon, COUNT(n.id) AS count
|
|
FROM contexts c
|
|
LEFT JOIN nodes n ON n.context_id = c.id
|
|
WHERE lower(c.name) = lower(?)
|
|
GROUP BY c.id
|
|
`).get(trimmed);
|
|
return mapContext(row);
|
|
}
|
|
|
|
function createContext({ name, description = null, icon = null }) {
|
|
const db = getDb();
|
|
const trimmedName = typeof name === 'string' ? name.trim() : '';
|
|
if (!trimmedName) {
|
|
throw new Error('Context name is required.');
|
|
}
|
|
const existingCount = Number(db.prepare('SELECT COUNT(*) AS count FROM contexts').get()?.count ?? 0);
|
|
if (existingCount >= MAX_CONTEXTS_PER_ACCOUNT) {
|
|
throw new Error(`Context limit reached. Maximum ${MAX_CONTEXTS_PER_ACCOUNT} contexts are allowed per account.`);
|
|
}
|
|
|
|
const now = new Date().toISOString();
|
|
const info = db.prepare(`
|
|
INSERT INTO contexts (name, description, icon, created_at, updated_at)
|
|
VALUES (?, ?, ?, ?, ?)
|
|
`).run(trimmedName, description ?? null, icon ?? null, now, now);
|
|
|
|
return getContextById(Number(info.lastInsertRowid));
|
|
}
|
|
|
|
function updateContext({ id, name, description, icon }) {
|
|
const db = getDb();
|
|
const existing = getContextById(id);
|
|
if (!existing) {
|
|
throw new Error(`Context ${id} not found.`);
|
|
}
|
|
|
|
const nextName = typeof name === 'string' && name.trim() ? name.trim() : existing.name;
|
|
const nextDescription = description === undefined ? existing.description : description;
|
|
const nextIcon = icon === undefined ? existing.icon : icon;
|
|
const now = new Date().toISOString();
|
|
|
|
db.prepare(`
|
|
UPDATE contexts
|
|
SET name = ?, description = ?, icon = ?, updated_at = ?
|
|
WHERE id = ?
|
|
`).run(nextName, nextDescription ?? null, nextIcon ?? null, now, id);
|
|
|
|
return getContextById(id);
|
|
}
|
|
|
|
function resolveContextId(input = {}) {
|
|
const hasContextId = Object.prototype.hasOwnProperty.call(input, 'context_id');
|
|
const hasContextName = typeof input.context_name === 'string' && input.context_name.trim().length > 0;
|
|
|
|
if (!hasContextId && !hasContextName) {
|
|
return undefined;
|
|
}
|
|
|
|
if (hasContextId && input.context_id === null) {
|
|
if (hasContextName) {
|
|
throw new Error('context_name cannot be combined with context_id: null.');
|
|
}
|
|
return null;
|
|
}
|
|
|
|
let resolvedById = null;
|
|
if (hasContextId) {
|
|
resolvedById = getContextById(input.context_id);
|
|
if (!resolvedById) {
|
|
throw new Error(`Context ${input.context_id} not found.`);
|
|
}
|
|
}
|
|
|
|
if (!hasContextName) {
|
|
return resolvedById ? resolvedById.id : undefined;
|
|
}
|
|
|
|
const byName = getContextByName(input.context_name);
|
|
if (!byName) {
|
|
throw new Error(`Context "${input.context_name}" not found.`);
|
|
}
|
|
if (resolvedById && resolvedById.id !== byName.id) {
|
|
throw new Error('context_id and context_name refer to different contexts.');
|
|
}
|
|
return byName.id;
|
|
}
|
|
|
|
module.exports = {
|
|
MAX_CONTEXTS_PER_ACCOUNT,
|
|
listContexts,
|
|
getContextById,
|
|
getContextByName,
|
|
createContext,
|
|
updateContext,
|
|
resolveContextId,
|
|
};
|