UNPKG

@gensx/storage

Version:

Cloud storage, blobs, sqlite, and vector database providers/hooks for GenSX.

299 lines (295 loc) 11.3 kB
/** * Check out the docs at https://www.gensx.com/docs * Find us on Github https://github.com/gensx-inc/gensx * Find us on Discord https://discord.gg/F5BSU8Kc */ import { readConfig } from '@gensx/core'; import { parseErrorResponse } from '../utils/parse-error.js'; import { USER_AGENT } from '../utils/user-agent.js'; import { DatabaseInternalError, DatabaseError, DatabaseNetworkError } from './types.js'; /* eslint-disable @typescript-eslint/only-throw-error */ /** * Base URL for the GenSX Console API */ const API_BASE_URL = "https://api.gensx.com"; /** * Helper to convert between API errors and DatabaseErrors */ function handleApiError(err, operation) { if (err instanceof DatabaseError) { throw err; } if (err instanceof Error) { throw new DatabaseNetworkError(`Error during ${operation}: ${err.message}`, err); } throw new DatabaseNetworkError(`Error during ${operation}: ${String(err)}`); } /** * Implementation of Database interface for remote cloud storage */ class RemoteDatabase { databaseName; baseUrl; apiKey; org; project; environment; constructor(databaseName, baseUrl, apiKey, org, project, environment) { this.databaseName = encodeURIComponent(databaseName); this.baseUrl = baseUrl; this.apiKey = apiKey; this.org = org; this.project = project; this.environment = environment; } async execute(sql, params) { try { const response = await fetch(`${this.baseUrl}/org/${this.org}/projects/${this.project}/environments/${this.environment}/database/${this.databaseName}/execute`, { method: "POST", headers: { Authorization: `Bearer ${this.apiKey}`, "Content-Type": "application/json", "User-Agent": USER_AGENT, }, body: JSON.stringify({ sql, params, }), }); if (!response.ok) { const message = await parseErrorResponse(response); throw new DatabaseInternalError(`Failed to execute SQL: ${message}`); } const data = (await response.json()); return data; } catch (err) { throw handleApiError(err, "execute"); } } async batch(statements) { try { const response = await fetch(`${this.baseUrl}/org/${this.org}/projects/${this.project}/environments/${this.environment}/database/${this.databaseName}/batch`, { method: "POST", headers: { Authorization: `Bearer ${this.apiKey}`, "Content-Type": "application/json", "User-Agent": USER_AGENT, }, body: JSON.stringify({ statements }), }); if (!response.ok) { const message = await parseErrorResponse(response); throw new DatabaseInternalError(`Failed to execute batch: ${message}`); } const data = (await response.json()); return data; } catch (err) { throw handleApiError(err, "batch"); } } async executeMultiple(sql) { try { const response = await fetch(`${this.baseUrl}/org/${this.org}/projects/${this.project}/environments/${this.environment}/database/${this.databaseName}/multiple`, { method: "POST", headers: { Authorization: `Bearer ${this.apiKey}`, "Content-Type": "application/json", "User-Agent": USER_AGENT, }, body: JSON.stringify({ sql }), }); if (!response.ok) { const message = await parseErrorResponse(response); throw new DatabaseInternalError(`Failed to execute multiple: ${message}`); } const data = (await response.json()); return data; } catch (err) { throw handleApiError(err, "executeMultiple"); } } async migrate(sql) { try { const response = await fetch(`${this.baseUrl}/org/${this.org}/projects/${this.project}/environments/${this.environment}/database/${this.databaseName}/migrate`, { method: "POST", headers: { Authorization: `Bearer ${this.apiKey}`, "Content-Type": "application/json", "User-Agent": USER_AGENT, }, body: JSON.stringify({ sql }), }); if (!response.ok) { const message = await parseErrorResponse(response); throw new DatabaseInternalError(`Failed to execute migration: ${message}`); } const data = (await response.json()); return data; } catch (err) { throw handleApiError(err, "migrate"); } } async getInfo() { try { const response = await fetch(`${this.baseUrl}/org/${this.org}/projects/${this.project}/environments/${this.environment}/database/${this.databaseName}/info`, { method: "GET", headers: { Authorization: `Bearer ${this.apiKey}`, "User-Agent": USER_AGENT, }, }); if (!response.ok) { const message = await parseErrorResponse(response); throw new DatabaseInternalError(`Failed to get database info: ${message}`); } const data = (await response.json()); // Convert date string to Date object const { lastModified, ...rest } = data; return { ...rest, lastModified: new Date(lastModified), }; } catch (err) { throw handleApiError(err, "getInfo"); } } close() { // No-op for remote database - connection is managed by API return; } } /** * Implementation of DatabaseStorage interface for remote cloud storage */ class RemoteDatabaseStorage { apiKey; apiBaseUrl; org; project; environment; databases = new Map(); constructor(project, environment) { this.project = project; this.environment = environment; const config = readConfig(); this.apiKey = process.env.GENSX_API_KEY ?? config.api?.token ?? ""; if (!this.apiKey) { throw new Error("GENSX_API_KEY environment variable must be set for cloud storage"); } this.org = process.env.GENSX_ORG ?? config.api?.org ?? ""; if (!this.org) { throw new Error("Organization ID must be provided via props or GENSX_ORG environment variable"); } this.apiBaseUrl = process.env.GENSX_API_BASE_URL ?? config.api?.baseUrl ?? API_BASE_URL; } getDatabase(name) { if (!this.databases.has(name)) { this.databases.set(name, new RemoteDatabase(name, this.apiBaseUrl, this.apiKey, this.org, this.project, this.environment)); } return this.databases.get(name); } async listDatabases(options) { try { const url = new URL(`${this.apiBaseUrl}/org/${this.org}/projects/${this.project}/environments/${this.environment}/database`); if (options?.limit) { url.searchParams.append("limit", options.limit.toString()); } if (options?.cursor) { url.searchParams.append("cursor", options.cursor); } const response = await fetch(url.toString(), { method: "GET", headers: { Authorization: `Bearer ${this.apiKey}`, }, }); if (!response.ok) { const message = await parseErrorResponse(response); throw new DatabaseInternalError(`Failed to list databases: ${message}`); } const data = (await response.json()); return { databases: data.databases.map((db) => ({ name: decodeURIComponent(db.name), createdAt: new Date(db.createdAt), })), ...(data.nextCursor && { nextCursor: data.nextCursor }), }; } catch (err) { throw handleApiError(err, "listDatabases"); } } async ensureDatabase(name) { try { const response = await fetch(`${this.apiBaseUrl}/org/${this.org}/projects/${this.project}/environments/${this.environment}/database/${encodeURIComponent(name)}/ensure`, { method: "POST", headers: { Authorization: `Bearer ${this.apiKey}`, "Content-Type": "application/json", "User-Agent": USER_AGENT, }, }); if (!response.ok) { const message = await parseErrorResponse(response); throw new DatabaseInternalError(`Failed to ensure database: ${message}`); } const data = (await response.json()); // Make sure the database is in our cache if (!this.databases.has(name)) { this.getDatabase(name); } return data; } catch (err) { if (err instanceof DatabaseError) { throw err; } throw new DatabaseNetworkError(`Error during ensureDatabase operation: ${String(err)}`, err); } } hasEnsuredDatabase(name) { return this.databases.has(name); } async deleteDatabase(name) { try { const response = await fetch(`${this.apiBaseUrl}/org/${this.org}/projects/${this.project}/environments/${this.environment}/database/${encodeURIComponent(name)}`, { method: "DELETE", headers: { Authorization: `Bearer ${this.apiKey}`, "User-Agent": USER_AGENT, }, }); if (!response.ok) { const message = await parseErrorResponse(response); throw new DatabaseInternalError(`Failed to delete database: ${message}`); } const data = (await response.json()); // Remove database from caches if it was successfully deleted if (data.deleted) { if (this.databases.has(name)) { const db = this.databases.get(name); if (db) { db.close(); this.databases.delete(name); } } } return data; } catch (err) { if (err instanceof DatabaseError) { throw err; } throw new DatabaseNetworkError(`Error during deleteDatabase operation: ${String(err)}`, err); } } } export { RemoteDatabase, RemoteDatabaseStorage }; //# sourceMappingURL=remote.js.map