@gensx/storage
Version:
Cloud storage, blobs, sqlite, and vector database providers/hooks for GenSX.
299 lines (295 loc) • 11.3 kB
JavaScript
/**
* 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