UNPKG

@agentdb/sdk

Version:

JavaScript SDK for AgentDB database service

472 lines (425 loc) 15.3 kB
/** * AgentDB JavaScript SDK - CommonJS Version * * 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 * const { createVectorBuffer } = require('@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 */ constructor(baseUrl, apiKey) { 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; } /** * 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 }; 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); } 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)}`); 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); } /** * 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 */ 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 */ 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 */ 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, and expiresIn */ 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, and expiresIn */ 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 }) }); } } /** * 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') */ constructor(baseUrl, apiKey, token, dbName, dbType = 'sqlite') { 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; } /** * 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 }; 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); } 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 */ 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 }; return await this._makeRequest('/db/api/execute', { method: 'POST', body: JSON.stringify(requestBody) }); } } // CommonJS exports module.exports = { DatabaseService, DatabaseConnection, createVectorBuffer, AgentDBError, AuthenticationError, ValidationError, DatabaseError }; // Default export for convenience module.exports.default = DatabaseService;