/** * Create Command * * Create a new database on postgres.do * * @module cli/commands/create */ import { requireToken } from '../cli-auth.js' import { getApiUrl, addRecentDatabase } from '../config.js' import { printResult, printInfo, printVerbose, type OutputOptions, type CommandResult } from '../output.js' /** * Create command options */ export interface CreateOptions extends OutputOptions { /** Database name */ name: string /** Region for the database */ region?: string | undefined /** Database plan */ plan?: 'free' | 'pro' | 'enterprise' | undefined /** Wait for database to be ready */ wait?: boolean | undefined /** Timeout for waiting (ms) */ timeout?: number | undefined /** API URL override */ apiUrl?: string | undefined } /** * Database creation result */ export interface DatabaseInfo { id: string name: string status: 'creating' | 'ready' | 'error' region: string plan: string connectionUrl?: string createdAt: string } /** * Create a new database */ export async function runCreate(options: CreateOptions): Promise { const { name, region, plan, wait = true, timeout = 60000 } = options // Validate database name if (!name) { printResult({ success: false, error: 'Database name is required', }, options) process.exit(1) } if (!/^[a-z][a-z0-9_-]{0,62}$/.test(name)) { printResult({ success: false, error: 'Database name must start with a letter, contain only lowercase letters, numbers, underscores, and hyphens, and be 1-63 characters long', }, options) process.exit(1) } try { const token = requireToken(options.apiUrl) const apiUrl = getApiUrl(options.apiUrl) printInfo(`Creating database '${name}'...`, options) printVerbose(`API URL: ${apiUrl}`, options) // Create the database const response = await fetch(`${apiUrl}/v1/databases`, { method: 'POST', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ name, region: region || 'auto', plan: plan || 'free', }), }) if (!response.ok) { const error = await response.json().catch(() => ({ message: 'Failed to create database' })) printResult({ success: false, error: (error as { message?: string; error?: string }).message || (error as { error?: string }).error || 'Failed to create database', }, options) process.exit(1) } let database = await response.json() as DatabaseInfo // Wait for database to be ready if requested if (wait && database.status === 'creating') { printInfo('Waiting for database to be ready...', options) const startTime = Date.now() while (database.status === 'creating' && Date.now() - startTime < timeout) { await new Promise((resolve) => setTimeout(resolve, 2000)) const statusResponse = await fetch(`${apiUrl}/v1/databases/${database.id}`, { headers: { 'Authorization': `Bearer ${token}`, }, }) if (statusResponse.ok) { database = await statusResponse.json() as DatabaseInfo printVerbose(`Status: ${database.status}`, options) } } if (database.status === 'creating') { printResult({ success: false, error: 'Timeout waiting for database to be ready', }, options) process.exit(1) } } // Add to recent databases addRecentDatabase(name) const result: CommandResult = { success: true, message: `Database '${name}' created successfully`, data: database, } printResult(result, options) if (database.connectionUrl && !options.format) { console.log('\nConnection URL:') console.log(` ${database.connectionUrl}`) console.log('\nUsage:') console.log(` postgres.do shell ${name}`) console.log(` export DATABASE_URL="${database.connectionUrl}"`) } } catch (error) { printResult({ success: false, error: error instanceof Error ? error.message : 'Unknown error creating database', }, options) process.exit(1) } } /** * List all databases */ export async function runList(options: OutputOptions & { apiUrl?: string }): Promise { try { const token = requireToken(options.apiUrl) const apiUrl = getApiUrl(options.apiUrl) printInfo('Fetching databases...', options) const response = await fetch(`${apiUrl}/v1/databases`, { headers: { 'Authorization': `Bearer ${token}`, }, }) if (!response.ok) { const error = await response.json().catch(() => ({ message: 'Failed to list databases' })) printResult({ success: false, error: (error as { message?: string }).message || 'Failed to list databases', }, options) process.exit(1) } const databases = await response.json() as DatabaseInfo[] printResult({ success: true, data: databases, }, options) } catch (error) { printResult({ success: false, error: error instanceof Error ? error.message : 'Unknown error listing databases', }, options) process.exit(1) } } /** * Get database info */ export async function runInfo( nameOrId: string, options: OutputOptions & { apiUrl?: string } ): Promise { try { const token = requireToken(options.apiUrl) const apiUrl = getApiUrl(options.apiUrl) printInfo(`Fetching database '${nameOrId}'...`, options) const response = await fetch(`${apiUrl}/v1/databases/${nameOrId}`, { headers: { 'Authorization': `Bearer ${token}`, }, }) if (!response.ok) { if (response.status === 404) { printResult({ success: false, error: `Database '${nameOrId}' not found`, }, options) } else { const error = await response.json().catch(() => ({ message: 'Failed to get database info' })) printResult({ success: false, error: (error as { message?: string }).message || 'Failed to get database info', }, options) } process.exit(1) } const database = await response.json() as DatabaseInfo printResult({ success: true, data: database, }, options) } catch (error) { printResult({ success: false, error: error instanceof Error ? error.message : 'Unknown error getting database info', }, options) process.exit(1) } } /** * Delete a database */ export async function runDelete( nameOrId: string, options: OutputOptions & { apiUrl?: string; force?: boolean } ): Promise { if (!options.force) { console.error('Warning: This will permanently delete the database and all its data.') console.error('Use --force to confirm deletion.') process.exit(1) } try { const token = requireToken(options.apiUrl) const apiUrl = getApiUrl(options.apiUrl) printInfo(`Deleting database '${nameOrId}'...`, options) const response = await fetch(`${apiUrl}/v1/databases/${nameOrId}`, { method: 'DELETE', headers: { 'Authorization': `Bearer ${token}`, }, }) if (!response.ok) { if (response.status === 404) { printResult({ success: false, error: `Database '${nameOrId}' not found`, }, options) } else { const error = await response.json().catch(() => ({ message: 'Failed to delete database' })) printResult({ success: false, error: (error as { message?: string }).message || 'Failed to delete database', }, options) } process.exit(1) } printResult({ success: true, message: `Database '${nameOrId}' deleted successfully`, }, options) } catch (error) { printResult({ success: false, error: error instanceof Error ? error.message : 'Unknown error deleting database', }, options) process.exit(1) } }