UNPKG

@agentdb/sdk

Version:

JavaScript SDK for AgentDB database service

780 lines (696 loc) 25.4 kB
/** * AgentDB JavaScript SDK * * A lightweight JavaScript SDK for the AgentDB database service. * Uses only the fetch API for HTTP requests. */ /** * Create a Float32 buffer from an array of numbers for use as vector parameters * @param {number[]} values - Array of numeric values to convert to a vector buffer * @returns {Buffer|Uint8Array} Buffer containing the vector data as Float32 values * @throws {ValidationError} If values is not an array or contains non-numeric values * * @example * import { createVectorBuffer } from '@agentdb/sdk'; * * // Create a vector buffer from an array * const embedding = createVectorBuffer([0.1, 0.2, 0.3, 0.4]); * * // Use in a vector search query * const result = await connection.execute({ * sql: 'SELECT id, text, vec_distance_cosine(embedding, ?) as distance FROM documents ORDER BY distance LIMIT 5', * params: [embedding] * }); */ function createVectorBuffer(values) { if (!Array.isArray(values)) { throw new ValidationError('createVectorBuffer expects an array of numbers'); } if (values.length === 0) { throw new ValidationError('createVectorBuffer expects a non-empty array'); } // Validate that all values are numbers for (let i = 0; i < values.length; i++) { if (typeof values[i] !== 'number' || !isFinite(values[i])) { throw new ValidationError(`createVectorBuffer expects all values to be finite numbers, got ${typeof values[i]} at index ${i}`); } } // Create buffer - use different approaches for Node.js vs browser if (typeof Buffer !== 'undefined') { // Node.js environment const buffer = Buffer.allocUnsafe(values.length * 4); for (let i = 0; i < values.length; i++) { buffer.writeFloatLE(values[i], i * 4); } return buffer; } else { // Browser environment - use ArrayBuffer and DataView const buffer = new ArrayBuffer(values.length * 4); const view = new DataView(buffer); for (let i = 0; i < values.length; i++) { view.setFloat32(i * 4, values[i], true); // true for little-endian } return new Uint8Array(buffer); } } /** * Custom error classes for AgentDB operations */ class AgentDBError extends Error { constructor(message, statusCode = null, response = null) { super(message); this.name = 'AgentDBError'; this.statusCode = statusCode; this.response = response; } } class AuthenticationError extends AgentDBError { constructor(message, statusCode = 401, response = null) { super(message, statusCode, response); this.name = 'AuthenticationError'; } } class ValidationError extends AgentDBError { constructor(message, statusCode = 400, response = null) { super(message, statusCode, response); this.name = 'ValidationError'; } } class DatabaseError extends AgentDBError { constructor(message, statusCode = 500, response = null) { super(message, statusCode, response); this.name = 'DatabaseError'; } } /** * Main database service class for listing databases and creating connections */ class DatabaseService { /** * Create a new DatabaseService instance * @param {string} baseUrl - The base URL of the AgentDB service (e.g., 'https://api.agentdb.dev') * @param {string} apiKey - The API key for authentication * @param {boolean} debug - Enable debug logging (default: false) */ constructor(baseUrl, apiKey, debug = false) { if (!baseUrl) { throw new ValidationError('baseUrl is required'); } if (!apiKey) { throw new ValidationError('apiKey is required'); } this.baseUrl = baseUrl.replace(/\/$/, ''); // Remove trailing slash this.apiKey = apiKey; this.debug = debug; } /** * Log debug messages when debug mode is enabled * @private */ _debugLog(...args) { if (this.debug) { console.log('[AgentDB SDK Debug]', ...args); } } /** * Make an authenticated HTTP request * @private */ async _makeRequest(endpoint, options = {}) { const url = `${this.baseUrl}${endpoint}`; const headers = { 'Authorization': `Bearer ${this.apiKey}`, 'Content-Type': 'application/json', ...options.headers }; // Add debug parameter to query string if debug mode is enabled and it's a GET request let finalUrl = url; if (this.debug && options.method === 'GET') { const separator = url.includes('?') ? '&' : '?'; finalUrl = `${url}${separator}debug=true`; } // Add debug parameter to request body if debug mode is enabled and it's not a GET request let finalOptions = { ...options }; if (this.debug && options.method !== 'GET' && options.body) { try { const bodyData = JSON.parse(options.body); bodyData.debug = true; finalOptions.body = JSON.stringify(bodyData); } catch (parseError) { // If body is not JSON, we can't add debug parameter this._debugLog('Warning: Could not add debug parameter to non-JSON request body'); } } this._debugLog(`Making ${finalOptions.method || 'GET'} request to: ${finalUrl}`); if (finalOptions.body) { this._debugLog('Request body:', finalOptions.body); } try { const response = await fetch(finalUrl, { ...finalOptions, headers }); let responseData; try { responseData = await response.json(); } catch (parseError) { throw new AgentDBError(`Failed to parse response: ${parseError.message}`, response.status); } this._debugLog('Response status:', response.status); this._debugLog('Response data:', responseData); // Log debug information from API if available if (responseData.debugLogs && Array.isArray(responseData.debugLogs)) { this._debugLog('API Debug Logs:'); responseData.debugLogs.forEach(log => { this._debugLog(`[${log.level}] ${log.timestamp}: ${log.message}`); }); } if (!response.ok) { const errorMessage = responseData.error || `HTTP ${response.status}: ${response.statusText}`; if (response.status === 401) { throw new AuthenticationError(errorMessage, response.status, responseData); } else if (response.status === 400) { throw new ValidationError(errorMessage, response.status, responseData); } else if (response.status >= 500) { throw new DatabaseError(errorMessage, response.status, responseData); } else { throw new AgentDBError(errorMessage, response.status, responseData); } } return responseData; } catch (error) { if (error instanceof AgentDBError) { throw error; } // Network or other fetch errors throw new AgentDBError(`Network error: ${error.message}`); } } /** * List all databases for a given token * @param {string} token - The UUID token for database access * @returns {Promise<Array>} Array of database objects with name, type, fileName, size, and modified properties */ async listDatabases(token) { if (!token) { throw new ValidationError('token is required'); } const response = await this._makeRequest(`/db/api/list-databases?token=${encodeURIComponent(token)}`, { method: 'GET' }); return response.databases || []; } /** * Create a new database connection * @param {string} token - The UUID token for database access * @param {string} dbName - The name of the database * @param {string} dbType - The type of database ('sqlite' or 'duckdb') * @returns {DatabaseConnection} A new database connection instance */ connect(token, dbName, dbType = 'sqlite') { return new DatabaseConnection(this.baseUrl, this.apiKey, token, dbName, dbType, this.debug); } /** * Delete a database * @param {string} token - The UUID token for database access * @param {string} dbName - The name of the database to delete * @param {string} dbType - The type of database ('sqlite' or 'duckdb') * @returns {Promise<Object>} Deletion result with success message and optional debug logs */ async deleteDatabase(token, dbName, dbType = 'sqlite') { if (!token) { throw new ValidationError('token is required'); } if (!dbName) { throw new ValidationError('dbName is required'); } if (!['sqlite', 'duckdb'].includes(dbType)) { throw new ValidationError('dbType must be either "sqlite" or "duckdb"'); } return await this._makeRequest('/db/api/delete-database', { method: 'DELETE', body: JSON.stringify({ token, dbName, dbType }) }); } /** * Rename a database * @param {string} token - The UUID token for database access * @param {string} oldDbName - The current name of the database * @param {string} newDbName - The new name for the database * @param {string} dbType - The type of database ('sqlite' or 'duckdb') * @returns {Promise<Object>} Rename result with success message and optional debug logs */ async renameDatabase(token, oldDbName, newDbName, dbType = 'sqlite') { if (!token) { throw new ValidationError('token is required'); } if (!oldDbName) { throw new ValidationError('oldDbName is required'); } if (!newDbName) { throw new ValidationError('newDbName is required'); } if (!['sqlite', 'duckdb'].includes(dbType)) { throw new ValidationError('dbType must be either "sqlite" or "duckdb"'); } return await this._makeRequest('/db/api/rename-database', { method: 'PUT', body: JSON.stringify({ token, oldDbName, newDbName, dbType }) }); } /** * Copy a database from one location to another * @param {string} sourceToken - The UUID token for the source database * @param {string} sourceDbName - The name of the source database * @param {string} sourceDbType - The type of the source database ('sqlite' or 'duckdb') * @param {string} destToken - The UUID token for the destination * @param {string} destDbName - The name for the destination database * @returns {Promise<Object>} Copy result with success message and optional debug logs */ async copyDatabase(sourceToken, sourceDbName, sourceDbType, destToken, destDbName) { if (!sourceToken) { throw new ValidationError('sourceToken is required'); } if (!sourceDbName) { throw new ValidationError('sourceDbName is required'); } if (!sourceDbType) { throw new ValidationError('sourceDbType is required'); } if (!destToken) { throw new ValidationError('destToken is required'); } if (!destDbName) { throw new ValidationError('destDbName is required'); } if (!['sqlite', 'duckdb'].includes(sourceDbType)) { throw new ValidationError('sourceDbType must be either "sqlite" or "duckdb"'); } return await this._makeRequest('/db/api/copy-database', { method: 'POST', body: JSON.stringify({ sourceToken, sourceDbName, sourceDbType, destToken, destDbName }) }); } /** * Get a presigned URL for uploading a database file * @param {string} token - The UUID token for database access * @param {string} dbName - The name for the database * @param {string} dbType - The type of database ('sqlite' or 'duckdb') * @returns {Promise<Object>} Upload URL information with uploadUrl, fileName, expiresIn, and optional debug logs */ async getUploadUrl(token, dbName, dbType = 'sqlite') { if (!token) { throw new ValidationError('token is required'); } if (!dbName) { throw new ValidationError('dbName is required'); } if (!['sqlite', 'duckdb'].includes(dbType)) { throw new ValidationError('dbType must be either "sqlite" or "duckdb"'); } return await this._makeRequest('/db/api/upload-url', { method: 'POST', body: JSON.stringify({ token, dbName, dbType }) }); } /** * Get a presigned URL for downloading a database file * @param {string} token - The UUID token for database access * @param {string} dbName - The name of the database * @param {string} dbType - The type of database ('sqlite' or 'duckdb') * @returns {Promise<Object>} Download URL information with downloadUrl, fileName, expiresIn, and optional debug logs */ async getDownloadUrl(token, dbName, dbType = 'sqlite') { if (!token) { throw new ValidationError('token is required'); } if (!dbName) { throw new ValidationError('dbName is required'); } if (!['sqlite', 'duckdb'].includes(dbType)) { throw new ValidationError('dbType must be either "sqlite" or "duckdb"'); } return await this._makeRequest('/db/api/download-url', { method: 'POST', body: JSON.stringify({ token, dbName, dbType }) }); } /** * Create a new database template * @param {string} templateName - The name for the template * @param {Array<string>} initializationSql - Array of SQL statements to initialize the template * @param {string} description - Description of the template * @returns {Promise<Object>} Created template information */ async createTemplate(templateName, initializationSql, description) { if (!templateName) { throw new ValidationError('templateName is required'); } if (!initializationSql || !Array.isArray(initializationSql)) { throw new ValidationError('initializationSql must be an array of SQL statements'); } if (!description) { throw new ValidationError('description is required'); } return await this._makeRequest('/api/templates', { method: 'POST', body: JSON.stringify({ templateName, initializationSql, description }) }); } /** * List all available templates * @returns {Promise<Array>} Array of template objects */ async listTemplates() { const response = await this._makeRequest('/api/templates', { method: 'GET' }); return response.templates || []; } /** * Get a specific template by name * @param {string} templateName - The name of the template to retrieve * @returns {Promise<Object>} Template information */ async getTemplate(templateName) { if (!templateName) { throw new ValidationError('templateName is required'); } return await this._makeRequest(`/api/templates/${encodeURIComponent(templateName)}`, { method: 'GET' }); } /** * Update an existing template * @param {string} templateName - The name of the template to update * @param {Array<string>} initializationSql - Array of SQL statements to initialize the template * @param {string} description - Description of the template * @returns {Promise<Object>} Update result with success message and old template info */ async updateTemplate(templateName, initializationSql, description) { if (!templateName) { throw new ValidationError('templateName is required'); } if (!initializationSql || !Array.isArray(initializationSql)) { throw new ValidationError('initializationSql must be an array of SQL statements'); } if (!description) { throw new ValidationError('description is required'); } return await this._makeRequest(`/api/templates/${encodeURIComponent(templateName)}`, { method: 'PUT', body: JSON.stringify({ initializationSql, description }) }); } /** * Delete a template * @param {string} templateName - The name of the template to delete * @returns {Promise<Object>} Deletion result with success message */ async deleteTemplate(templateName) { if (!templateName) { throw new ValidationError('templateName is required'); } return await this._makeRequest(`/api/templates/${encodeURIComponent(templateName)}`, { method: 'DELETE' }); } /** * Get template information for a specific database * @param {string} templateName - The name of the template * @param {string} token - The UUID token for database access * @param {string} dbName - The name of the database * @param {string} dbType - The type of database ('sqlite' or 'duckdb') * @returns {Promise<Object>} Template info including schema and application status */ async getTemplateInfo(templateName, token, dbName, dbType = 'sqlite') { if (!templateName) { throw new ValidationError('templateName is required'); } if (!token) { throw new ValidationError('token is required'); } if (!dbName) { throw new ValidationError('dbName is required'); } if (!['sqlite', 'duckdb'].includes(dbType)) { throw new ValidationError('dbType must be either "sqlite" or "duckdb"'); } const params = new URLSearchParams({ templateName, token, dbName, dbType }); return await this._makeRequest(`/db/api/template-info?${params.toString()}`, { method: 'GET' }); } /** * Get applied templates for a database * @param {string} token - The UUID token for database access * @param {string} dbName - The name of the database * @param {string} dbType - The type of database ('sqlite' or 'duckdb') * @returns {Promise<Object>} Applied templates information */ async getAppliedTemplates(token, dbName, dbType = 'sqlite') { if (!token) { throw new ValidationError('token is required'); } if (!dbName) { throw new ValidationError('dbName is required'); } if (!['sqlite', 'duckdb'].includes(dbType)) { throw new ValidationError('dbType must be either "sqlite" or "duckdb"'); } const params = new URLSearchParams({ token, dbName, dbType }); return await this._makeRequest(`/db/api/applied-templates?${params.toString()}`, { method: 'GET' }); } /** * Create a permanent URL slug for MCP server endpoints * @param {Object} queryParams - Query parameters for the MCP endpoint * @returns {Promise<Object>} Created slug information with slug and full URL */ async createMcpSlug(queryParams) { if (!queryParams || typeof queryParams !== 'object') { throw new ValidationError('queryParams is required and must be an object'); } return await this._makeRequest('/api/mcp/create-slug', { method: 'POST', body: JSON.stringify({ queryParams }) }); } /** * Get MCP slug information * @param {string} slug - The slug identifier * @returns {Promise<Object>} Slug information and query parameters */ async getMcpSlug(slug) { if (!slug) { throw new ValidationError('slug is required'); } return await this._makeRequest(`/api/mcp/slug/${encodeURIComponent(slug)}`, { method: 'GET' }); } } /** * Database connection class for executing SQL statements */ class DatabaseConnection { /** * Create a new DatabaseConnection instance * @param {string} baseUrl - The base URL of the AgentDB service * @param {string} apiKey - The API key for authentication * @param {string} token - The UUID token for database access * @param {string} dbName - The name of the database * @param {string} dbType - The type of database ('sqlite' or 'duckdb') * @param {boolean} debug - Enable debug logging (default: false) */ constructor(baseUrl, apiKey, token, dbName, dbType = 'sqlite', debug = false) { if (!baseUrl) { throw new ValidationError('baseUrl is required'); } if (!apiKey) { throw new ValidationError('apiKey is required'); } if (!token) { throw new ValidationError('token is required'); } if (!dbName) { throw new ValidationError('dbName is required'); } if (!['sqlite', 'duckdb'].includes(dbType)) { throw new ValidationError('dbType must be either "sqlite" or "duckdb"'); } this.baseUrl = baseUrl.replace(/\/$/, ''); this.apiKey = apiKey; this.token = token; this.dbName = dbName; this.dbType = dbType; this.debug = debug; } /** * Log debug messages when debug mode is enabled * @private */ _debugLog(...args) { if (this.debug) { console.log('[AgentDB SDK Debug]', ...args); } } /** * Make an authenticated HTTP request * @private */ async _makeRequest(endpoint, options = {}) { const url = `${this.baseUrl}${endpoint}`; const headers = { 'Authorization': `Bearer ${this.apiKey}`, 'Content-Type': 'application/json', ...options.headers }; this._debugLog(`Making ${options.method || 'GET'} request to: ${url}`); if (options.body) { this._debugLog('Request body:', options.body); } try { const response = await fetch(url, { ...options, headers }); let responseData; try { responseData = await response.json(); } catch (parseError) { throw new AgentDBError(`Failed to parse response: ${parseError.message}`, response.status); } this._debugLog('Response status:', response.status); this._debugLog('Response data:', responseData); // Log debug information from API if available if (responseData.debugLogs && Array.isArray(responseData.debugLogs)) { this._debugLog('API Debug Logs:'); responseData.debugLogs.forEach(log => { this._debugLog(`[${log.level}] ${log.timestamp}: ${log.message}`); }); } if (!response.ok) { const errorMessage = responseData.error || `HTTP ${response.status}: ${response.statusText}`; if (response.status === 401) { throw new AuthenticationError(errorMessage, response.status, responseData); } else if (response.status === 400) { throw new ValidationError(errorMessage, response.status, responseData); } else if (response.status >= 500) { throw new DatabaseError(errorMessage, response.status, responseData); } else { throw new AgentDBError(errorMessage, response.status, responseData); } } return responseData; } catch (error) { if (error instanceof AgentDBError) { throw error; } // Network or other fetch errors throw new AgentDBError(`Network error: ${error.message}`); } } /** * Execute one or more SQL statements * @param {Array|Object} statements - Array of statement objects or single statement object * Each statement should have 'sql' and optional 'params' properties * @returns {Promise<Object>} Execution results with results array and optional debug logs */ async execute(statements) { if (!statements) { throw new ValidationError('statements is required'); } // Normalize to array format const statementsArray = Array.isArray(statements) ? statements : [statements]; // Validate statements format for (const stmt of statementsArray) { if (!stmt || typeof stmt !== 'object') { throw new ValidationError('Each statement must be an object'); } if (!stmt.sql || typeof stmt.sql !== 'string') { throw new ValidationError('Each statement must have a "sql" property with a string value'); } if (stmt.params && !Array.isArray(stmt.params)) { throw new ValidationError('Statement params must be an array if provided'); } } const requestBody = { token: this.token, dbName: this.dbName, dbType: this.dbType, statements: statementsArray }; // Add debug parameter if debug mode is enabled if (this.debug) { requestBody.debug = true; } return await this._makeRequest('/db/api/execute', { method: 'POST', body: JSON.stringify(requestBody) }); } /** * Convert natural language to SQL and optionally execute it * @param {string} query - The natural language query * @param {Array} conversationHistory - Optional conversation history for context * @param {string} templateName - Optional template name for schema context * @returns {Promise<Object>} Natural language conversion results with generated SQL and optional execution results */ async naturalLanguageToSql(query, conversationHistory = null, templateName = null) { if (!query || typeof query !== 'string') { throw new ValidationError('query is required and must be a string'); } if (conversationHistory && !Array.isArray(conversationHistory)) { throw new ValidationError('conversationHistory must be an array if provided'); } const requestBody = { query, token: this.token, dbName: this.dbName, dbType: this.dbType }; if (conversationHistory && conversationHistory.length > 0) { requestBody.conversationHistory = conversationHistory; } if (templateName) { requestBody.templateName = templateName; } // Add debug parameter if debug mode is enabled if (this.debug) { requestBody.debug = true; } return await this._makeRequest('/db/api/nl-to-sql', { method: 'POST', body: JSON.stringify(requestBody) }); } } // Export classes, utility functions, and error types export { DatabaseService, DatabaseConnection, createVectorBuffer, AgentDBError, AuthenticationError, ValidationError, DatabaseError }; // Default export for convenience export default DatabaseService;