cline-sdk
Version:
Comprehensive SDK for Cline - AI-powered development assistant with database integration, execution modes, and custom functions
392 lines • 15.8 kB
JavaScript
;
/**
* Database Schema Tool
*
* Allows the AI to inspect and understand database schemas, tables, and relationships
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.DatabaseSchemaTool = void 0;
class DatabaseSchemaTool {
constructor(dbManager) {
this.dbManager = dbManager;
}
async execute(input) {
const startTime = Date.now();
try {
const { action, schemaName, tableName, searchTerm, includeData = false } = input;
switch (action) {
case "listSchemas":
return this.listSchemas(startTime);
case "listTables":
return this.listTables(schemaName, startTime);
case "describeTable":
return this.describeTable(schemaName, tableName, includeData, startTime);
case "getRelationships":
return this.getRelationships(schemaName, tableName, startTime);
case "searchTables":
return this.searchTables(searchTerm, startTime);
case "getContext":
return this.getFullContext(startTime);
default:
return {
success: false,
error: `Unknown action: ${action}`,
executionTime: Date.now() - startTime,
};
}
}
catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : "Unknown error",
executionTime: Date.now() - startTime,
};
}
}
async listSchemas(startTime) {
// Get fresh schema information
const schemas = await this.dbManager.introspectDatabase();
const schemaList = Array.from(schemas.keys()).map((schemaName) => ({
schemaName,
tableCount: schemas.get(schemaName)?.length || 0,
tables: schemas.get(schemaName)?.map((t) => t.tableName) || [],
}));
return {
success: true,
data: schemaList,
executionTime: Date.now() - startTime,
};
}
async listTables(schemaName, startTime) {
const schemas = await this.dbManager.introspectDatabase();
if (schemaName) {
const tables = schemas.get(schemaName);
if (!tables) {
return {
success: false,
error: `Schema '${schemaName}' not found`,
executionTime: Date.now() - startTime,
};
}
const tableList = tables.map((table) => ({
tableName: table.tableName,
description: table.description,
columnCount: table.columns.length,
primaryKeys: table.primaryKeys,
hasRLS: table.rowLevelSecurity,
permissions: table.permissions,
}));
return {
success: true,
data: tableList,
executionTime: Date.now() - startTime,
};
}
else {
// List all tables from all schemas
const allTables = [];
for (const [schema, tables] of schemas) {
for (const table of tables) {
allTables.push({
schemaName: schema,
tableName: table.tableName,
description: table.description,
columnCount: table.columns.length,
primaryKeys: table.primaryKeys,
hasRLS: table.rowLevelSecurity,
});
}
}
return {
success: true,
data: allTables,
executionTime: Date.now() - startTime,
};
}
}
async describeTable(schemaName, tableName, includeData, startTime) {
if (!schemaName || !tableName) {
return {
success: false,
error: "Both schemaName and tableName are required for describeTable action",
executionTime: Date.now() - startTime,
};
}
const schemas = await this.dbManager.introspectDatabase();
const tables = schemas.get(schemaName);
if (!tables) {
return {
success: false,
error: `Schema '${schemaName}' not found`,
executionTime: Date.now() - startTime,
};
}
const table = tables.find((t) => t.tableName === tableName);
if (!table) {
return {
success: false,
error: `Table '${tableName}' not found in schema '${schemaName}'`,
executionTime: Date.now() - startTime,
};
}
let sampleData = [];
if (includeData) {
try {
const sampleResult = await this.dbManager.executeQuery(`SELECT * FROM "${schemaName}"."${tableName}" LIMIT 5`);
sampleData = sampleResult.data || [];
}
catch (error) {
console.warn("Could not fetch sample data:", error);
}
}
const tableInfo = {
...table,
sampleData: includeData ? sampleData : undefined,
relationships: this.getTableRelationships(table),
usage: {
queryHint: `To query this table: SELECT * FROM "${schemaName}"."${tableName}"`,
insertHint: table.permissions.insert
? `To insert: INSERT INTO "${schemaName}"."${tableName}" (${table.columns
.filter((c) => !c.isPrimaryKey || c.defaultValue)
.map((c) => c.columnName)
.join(", ")}) VALUES (...)`
: "Insert not permitted",
updateHint: table.permissions.update
? `To update: UPDATE "${schemaName}"."${tableName}" SET column = value WHERE condition`
: "Update not permitted",
deleteHint: table.permissions.delete
? `To delete: DELETE FROM "${schemaName}"."${tableName}" WHERE condition`
: "Delete not permitted",
},
};
return {
success: true,
data: tableInfo,
executionTime: Date.now() - startTime,
};
}
getTableRelationships(table) {
return {
foreignKeys: table.foreignKeys.map((fk) => ({
column: fk.columnName,
references: `${fk.referencedTable}.${fk.referencedColumn}`,
constraint: fk.constraintName,
onDelete: fk.onDelete,
onUpdate: fk.onUpdate,
})),
indexes: table.indexes.map((idx) => ({
name: idx.indexName,
columns: idx.columns,
unique: idx.isUnique,
type: idx.indexType,
})),
constraints: table.constraints.map((cons) => ({
name: cons.constraintName,
type: cons.constraintType,
columns: cons.columns,
definition: cons.definition,
})),
};
}
async getRelationships(schemaName, tableName, startTime) {
const schemas = await this.dbManager.introspectDatabase();
if (schemaName && tableName) {
// Get relationships for specific table
const tables = schemas.get(schemaName);
const table = tables?.find((t) => t.tableName === tableName);
if (!table) {
return {
success: false,
error: `Table '${tableName}' not found in schema '${schemaName}'`,
executionTime: Date.now() - startTime,
};
}
const relationships = this.getTableRelationships(table);
// Also find tables that reference this table
const referencingTables = [];
for (const [schema, schemaTables] of schemas) {
for (const schemaTable of schemaTables) {
for (const fk of schemaTable.foreignKeys) {
if (fk.referencedTable === tableName) {
referencingTables.push({
schema,
table: schemaTable.tableName,
column: fk.columnName,
constraint: fk.constraintName,
});
}
}
}
}
return {
success: true,
data: {
table: `${schemaName}.${tableName}`,
outgoingRelationships: relationships.foreignKeys,
incomingRelationships: referencingTables,
indexes: relationships.indexes,
constraints: relationships.constraints,
},
executionTime: Date.now() - startTime,
};
}
else {
// Get all relationships in database
const allRelationships = [];
for (const [schema, tables] of schemas) {
for (const table of tables) {
for (const fk of table.foreignKeys) {
allRelationships.push({
fromSchema: schema,
fromTable: table.tableName,
fromColumn: fk.columnName,
toTable: fk.referencedTable,
toColumn: fk.referencedColumn,
constraint: fk.constraintName,
onDelete: fk.onDelete,
onUpdate: fk.onUpdate,
});
}
}
}
return {
success: true,
data: allRelationships,
executionTime: Date.now() - startTime,
};
}
}
async searchTables(searchTerm, startTime) {
if (!searchTerm) {
return {
success: false,
error: "Search term is required",
executionTime: Date.now() - startTime,
};
}
const schemas = await this.dbManager.introspectDatabase();
const results = [];
const term = searchTerm.toLowerCase();
for (const [schemaName, tables] of schemas) {
for (const table of tables) {
// Search in table name
if (table.tableName.toLowerCase().includes(term)) {
results.push({
type: "table",
schemaName,
tableName: table.tableName,
match: "table name",
description: table.description,
});
}
// Search in table description
if (table.description && table.description.toLowerCase().includes(term)) {
results.push({
type: "table",
schemaName,
tableName: table.tableName,
match: "table description",
description: table.description,
});
}
// Search in column names and descriptions
for (const column of table.columns) {
if (column.columnName.toLowerCase().includes(term)) {
results.push({
type: "column",
schemaName,
tableName: table.tableName,
columnName: column.columnName,
match: "column name",
dataType: column.dataType,
description: column.description,
});
}
if (column.description && column.description.toLowerCase().includes(term)) {
results.push({
type: "column",
schemaName,
tableName: table.tableName,
columnName: column.columnName,
match: "column description",
dataType: column.dataType,
description: column.description,
});
}
}
}
}
return {
success: true,
data: {
searchTerm,
resultsCount: results.length,
results: results.slice(0, 50), // Limit results
},
executionTime: Date.now() - startTime,
};
}
async getFullContext(startTime) {
const context = this.dbManager.getSchemaContext();
return {
success: true,
data: {
contextLength: context.length,
tables: await this.getTableSummary(),
},
context: context,
executionTime: Date.now() - startTime,
};
}
async getTableSummary() {
const schemas = await this.dbManager.introspectDatabase();
const summary = {};
for (const [schemaName, tables] of schemas) {
summary[schemaName] = tables.map((table) => ({
name: table.tableName,
columns: table.columns.length,
primaryKeys: table.primaryKeys.length,
foreignKeys: table.foreignKeys.length,
hasRLS: table.rowLevelSecurity,
permissions: table.permissions,
}));
}
return summary;
}
getDefinition() {
return {
name: "DatabaseSchema",
description: "Inspect database schemas, tables, columns, and relationships. Essential for understanding the database structure before writing queries.",
inputSchema: {
type: "object",
properties: {
action: {
type: "string",
description: "The action to perform",
enum: ["listSchemas", "listTables", "describeTable", "getRelationships", "searchTables", "getContext"],
},
schemaName: {
type: "string",
description: "Name of the database schema (required for some actions)",
},
tableName: {
type: "string",
description: "Name of the table (required for describeTable and getRelationships)",
},
searchTerm: {
type: "string",
description: "Search term for searchTables action",
},
includeData: {
type: "boolean",
description: "Whether to include sample data when describing a table",
default: false,
},
},
required: ["action"],
},
};
}
}
exports.DatabaseSchemaTool = DatabaseSchemaTool;
//# sourceMappingURL=database-schema-tool.js.map