UNPKG

niledatabase

Version:

Command line interface for Nile databases

306 lines (266 loc) 9.37 kB
import axios, { AxiosInstance } from 'axios'; import { Developer, Database, Credentials } from './types'; import { theme } from './colors'; export interface Tenant { id: string; name?: string; } export interface CreateTenantRequest { name: string; id?: string; } export interface NileAPIOptions { token: string | undefined; debug?: boolean; controlPlaneUrl?: string; dbHost?: string; } export interface PostgresConnection { host: string; database: string; user: string; password: string; port?: number; } export interface Workspace { id: string; slug: string; name: string; created?: string; } export interface CreateUserRequest { email: string; password: string; name?: string; givenName?: string; familyName?: string; picture?: string; } export interface User { id: string; email: string; name?: string; givenName?: string; familyName?: string; picture?: string; emailVerified?: string; created: string; updated: string; tenants?: string[]; } export interface UpdateUserRequest { name?: string; givenName?: string; familyName?: string; picture?: string; } export class NileAPI { private controlPlaneClient: AxiosInstance; private userClient: AxiosInstance; private static DEFAULT_CONTROL_PLANE_URL = 'https://global.thenile.dev'; private static DEFAULT_USER_URL = 'https://us-west-2.api.thenile.dev'; private debug: boolean; private token: string; private controlPlaneUrl: string; private userUrl: string; private dbHost?: string; constructor(options: NileAPIOptions) { this.debug = options.debug || false; if (!options.token) { throw new Error('Token is required'); } this.token = options.token; // Handle URL override if (options.controlPlaneUrl) { // Remove any protocol prefix if present this.controlPlaneUrl = options.controlPlaneUrl.replace(/^(https?:\/\/)/, ''); // Add https:// prefix this.controlPlaneUrl = `https://${this.controlPlaneUrl}`; } else { this.controlPlaneUrl = NileAPI.DEFAULT_CONTROL_PLANE_URL; } // Set user URL this.userUrl = NileAPI.DEFAULT_USER_URL; if (this.debug) { console.log(theme.dim('\nAPI Configuration:')); console.log(theme.dim('Control Plane URL:'), this.controlPlaneUrl); console.log(theme.dim('User API URL:'), this.userUrl); console.log(); } // Create control plane client for workspace/database operations this.controlPlaneClient = axios.create({ baseURL: this.controlPlaneUrl, headers: { Authorization: `Bearer ${this.token}`, 'Content-Type': 'application/json' }, }); // Create user client for user operations this.userClient = axios.create({ baseURL: this.userUrl, headers: { Authorization: `Bearer ${this.token}`, 'Content-Type': 'application/json' }, }); // Add debug logging this.addDebugLogging(this.controlPlaneClient); this.addDebugLogging(this.userClient); this.dbHost = options.dbHost; } private addDebugLogging(client: AxiosInstance) { client.interceptors.request.use(request => { if (this.debug) { console.log(theme.dim('\nAPI Request:')); console.log(theme.dim(`${request.method?.toUpperCase()} ${request.baseURL}${request.url}`)); console.log(theme.dim('Headers:'), { ...request.headers, Authorization: request.headers.Authorization ? 'Bearer [hidden]' : undefined }); if (request.data) { console.log(theme.dim('Body:'), request.data); } console.log(); } return request; }); client.interceptors.response.use(response => { if (this.debug) { console.log(theme.dim('API Response Status:'), response.status); if (response.data) { console.log(theme.dim('Response Data:'), response.data); } } return response; }, error => { if (this.debug && error.response) { console.log(theme.dim('API Error Response:'), { status: error.response.status, data: error.response.data }); } throw error; }); } async getDatabaseConnection(workspaceSlug: string, databaseName: string): Promise<PostgresConnection> { // Get database credentials from control plane if (this.debug) { console.log(theme.dim('\nFetching database credentials...')); } const credentials = await this.createDatabaseCredentials(workspaceSlug, databaseName); if (!credentials.id || !credentials.password) { throw new Error('Invalid credentials received from server'); } // Get the region from the database info const region = credentials.database.region; // e.g., AWS_US_WEST_2 or AZURE_EASTUS const regionParts = region.toLowerCase().split('_'); const cloudProvider = regionParts[0]; // aws or azure const regionId = regionParts.slice(1).join('-'); // e.g., us-west-2 or eastus // Use custom host if provided, otherwise construct based on cloud provider let dbHost; if (cloudProvider === 'azure') { dbHost = `db.${regionId}.azure.thenile.dev`; } else { // Default AWS format dbHost = `${regionId}.db.thenile.dev`; } if (this.debug) { console.log(theme.dim('\nPostgreSQL Connection Details:')); console.log(theme.dim('Cloud Provider:'), cloudProvider); console.log(theme.dim('Region:'), regionId); console.log(theme.dim('Host:'), dbHost); console.log(theme.dim('Database:'), databaseName); console.log(theme.dim('User:'), credentials.id); console.log(); } return { host: dbHost, database: databaseName, user: credentials.id, password: credentials.password, port: 5432 }; } async getDeveloperInfo(): Promise<Developer> { const response = await this.controlPlaneClient.get('/developers/me'); return response.data; } async listDatabases(workspaceSlug: string): Promise<Database[]> { const response = await this.controlPlaneClient.get(`/workspaces/${workspaceSlug}/databases`); return response.data; } async createDatabase(workspaceSlug: string, name: string, region: string): Promise<Database> { const response = await this.controlPlaneClient.post(`/workspaces/${workspaceSlug}/databases`, { databaseName: name, region, }); return response.data; } async getDatabase(workspaceSlug: string, databaseName: string): Promise<Database> { const response = await this.controlPlaneClient.get(`/workspaces/${workspaceSlug}/databases/${databaseName}`); return response.data; } async deleteDatabase(workspaceSlug: string, databaseName: string): Promise<void> { await this.controlPlaneClient.delete(`/workspaces/${workspaceSlug}/databases/${databaseName}`); } async listDatabaseCredentials(workspaceSlug: string, databaseName: string): Promise<Credentials[]> { const response = await this.controlPlaneClient.get( `/workspaces/${workspaceSlug}/databases/${databaseName}/credentials` ); return response.data; } async createDatabaseCredentials(workspaceSlug: string, databaseName: string): Promise<Credentials> { const response = await this.controlPlaneClient.post( `/workspaces/${workspaceSlug}/databases/${databaseName}/credentials` ); return response.data; } async listRegions(workspaceSlug: string): Promise<string[]> { const response = await this.controlPlaneClient.get(`/workspaces/${workspaceSlug}/regions`); return response.data; } async listWorkspaces(): Promise<Workspace[]> { const response = await this.controlPlaneClient.get('/workspaces'); return response.data; } async getWorkspace(workspaceSlug: string): Promise<Workspace> { const response = await this.controlPlaneClient.get(`/workspaces/${workspaceSlug}`); return response.data; } async getDatabaseId(workspaceSlug: string, databaseName: string): Promise<string> { const database = await this.getDatabase(workspaceSlug, databaseName); if (!database.id) { throw new Error(`Could not find database ID for database '${databaseName}'`); } return database.id; } async createUser(databaseId: string, user: CreateUserRequest, options?: { tenantId?: string; newTenantName?: string }): Promise<User> { const response = await this.userClient.post( `/v2/databases/${databaseId}/users`, user, { params: { tenantId: options?.tenantId, newTenantName: options?.newTenantName } } ); return response.data; } async getUserTenants(databaseId: string, userId: string): Promise<Tenant[]> { const response = await this.userClient.get(`/v2/databases/${databaseId}/users/${userId}/tenants`); return response.data; } async updateUser(databaseId: string, userId: string, updates: UpdateUserRequest): Promise<User> { const response = await this.userClient.patch( `/v2/databases/${databaseId}/users/${userId}`, updates ); return response.data; } async removeUserFromTenant(databaseId: string, userId: string, tenantId: string): Promise<void> { await this.userClient.delete( `/v2/databases/${databaseId}/users/${userId}/tenants/${tenantId}` ); } }