@agentdb/sdk
Version:
JavaScript SDK for AgentDB database service
472 lines (425 loc) • 15.3 kB
JavaScript
/**
* 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;