UNPKG

@voidagency/api-gateway-sdk

Version:

Node.js SDK for the DDEV Update Manager API - A powerful toolkit for managing DDEV instances, executing commands, running SQL queries, and PHP scripts with convenient template literal syntax

832 lines (675 loc) 21 kB
# DDEV Update Manager API SDK A Node.js SDK for interacting with the DDEV Update Manager API, built with the `got` HTTP client library. ## Installation ```bash npm install @voidagency/api-gateway-sdk ``` ## Quick Start ```javascript const DdevUpdateManagerSDK = require('@voidagency/api-gateway-sdk'); // Initialize the SDK with your API key const sdk = new DdevUpdateManagerSDK({ apiKey: 'your-api-key-here', baseUrl: 'http://localhost:3000' // optional, defaults to localhost:3000 }); // List all DDEV instances const instances = await sdk.listInstances(); console.log(instances); ``` ## Configuration The SDK constructor accepts the following options: - `apiKey` (required): Your API key for authentication - `baseUrl` (optional): Base URL of the API (default: `http://localhost:3000`) - `projectId` (optional): Set a default project ID for this SDK instance - `requestOptions` (optional): Additional options to pass to the `got` HTTP client ```javascript const sdk = new DdevUpdateManagerSDK({ apiKey: 'your-api-key', baseUrl: 'https://api.example.com', projectId: 'my-drupal-project', // Set default project ID requestOptions: { timeout: { request: 60000 }, // 60 second timeout retry: { limit: 5 } // 5 retry attempts } }); ``` ## Setting Project ID You can set the project ID in three different ways: ### Method 1: Constructor Option ```javascript const sdk = new DdevUpdateManagerSDK({ apiKey: 'your-api-key', projectId: 'my-drupal-project' }); ``` ### Method 2: Manual Setting ```javascript const sdk = new DdevUpdateManagerSDK({ apiKey: 'your-api-key' }); sdk.setProjectId('my-drupal-project'); ``` ### Method 3: By URL ```javascript const sdk = new DdevUpdateManagerSDK({ apiKey: 'your-api-key' }); const projectId = await sdk.getInstanceByUrl('https://my-drupal-project.ddev.site'); // projectId is now set to 'my-drupal-project' ``` Once a project ID is set, you can use methods without passing the projectId parameter: ```javascript // With projectId set on instance const sdk = new DdevUpdateManagerSDK({ apiKey: 'your-api-key', projectId: 'my-drupal-project' }); // No need to pass projectId to methods await sdk.getDrupalStatus(); await sdk.clearDrupalCache(); await sdk.execCommand('drush status'); ``` ## API Methods ### Core Methods #### `getHello()` Get a simple hello message from the API. ```javascript const message = await sdk.getHello(); console.log(message); // "Hello World!" ``` #### `listInstances()` List all DDEV instances. ```javascript const instances = await sdk.listInstances(); console.log(instances); // [ // { // name: "my-drupal-project", // status: "running", // type: "drupal9", // httpurl: "https://my-drupal-project.ddev.site" // } // ] ``` #### `getProject(projectId?)` Get detailed information about a specific DDEV project. ```javascript // With explicit projectId const project = await sdk.getProject('my-drupal-project'); // With instance projectId set const sdk = new DdevUpdateManagerSDK({ apiKey: 'your-api-key', projectId: 'my-drupal-project' }); const project = await sdk.getProject(); // Uses instance projectId console.log(project); // { // name: "my-drupal-project", // status: "running", // type: "drupal9", // php_version: "8.1", // database_type: "mysql", // database_version: "8.0" // } ``` #### `startProject(projectId?)` Start a DDEV project. ```javascript // With explicit projectId const result = await sdk.startProject('my-drupal-project'); // With instance projectId set const result = await sdk.startProject(); // Uses instance projectId console.log(result); // { // name: "my-drupal-project", // status: "running" // } ``` #### `execCommand(command, projectId?)` Execute a command in a DDEV project. ```javascript // With explicit projectId const result = await sdk.execCommand('drush status', 'my-drupal-project'); // With instance projectId set const result = await sdk.execCommand('drush status'); // Uses instance projectId console.log(result); // { // stdout: "Drupal version : 9.5.0\nPHP version : 8.1.0\nDatabase system : MySQL 8.0", // stderr: "", // exitCode: 0 // } ``` #### `execSqlQuery(query, projectId?)` Execute a SQL query in a DDEV project's database. ```javascript // With explicit projectId const result = await sdk.execSqlQuery('SELECT uid, name, mail FROM users_field_data WHERE uid = 1', 'my-drupal-project'); // With instance projectId set const result = await sdk.execSqlQuery('SELECT uid, name, mail FROM users_field_data WHERE uid = 1'); // Uses instance projectId console.log(result); // { // stdout: { // rows: [ // { // uid: 1, // name: "admin", // mail: "admin@example.com" // } // ] // }, // stderr: "", // exitCode: 0 // } ``` #### `runPhpScript(script, projectId?)` Run a PHP script in a DDEV project (Drupal will be automatically bootstrapped). ```javascript // With explicit projectId const script = ` $user = \Drupal\user\Entity\User::load(1); echo $user->getEmail(); `; const result = await sdk.runPhpScript(script, 'my-drupal-project'); // With instance projectId set const result = await sdk.runPhpScript(script); // Uses instance projectId console.log(result); // { // stdout: "admin@example.com", // stderr: "", // exitCode: 0 // } ``` ### Template Literal Aliases For even more convenient syntax, you can use template literal aliases: #### `$exec(command, projectId?)` Execute command using template literal syntax. ```javascript // Instead of: sdk.execCommand('ls -la', projectId) await sdk.$exec`ls -la` await sdk.$exec('ls -la') // Also works with regular strings ``` #### `$sql(query, projectId?)` Execute SQL query using template literal syntax. ```javascript // Instead of: sdk.execSqlQuery('SELECT * FROM users', projectId) await sdk.$sql`SELECT id FROM users` await sdk.$sql('SELECT * FROM users') // Also works with regular strings ``` #### `$php(script, projectId?)` Run PHP script using template literal syntax. ```javascript // Instead of: sdk.runPhpScript('echo "Hello";', projectId) await sdk.$php`echo "Hello from PHP!";` // Multi-line scripts work great with template literals await sdk.$php` $term_storage = \Drupal::entityTypeManager()->getStorage('taxonomy_term'); $terms = $term_storage->loadByProperties(['vid' => 'tags']); $count = count($terms); echo "Found {$count} taxonomy terms"; ` ``` ### Project ID Management Methods #### `setProjectId(projectId)` Set the project ID for this SDK instance. ```javascript sdk.setProjectId('my-drupal-project'); ``` #### `getProjectId()` Get the current project ID. ```javascript const projectId = sdk.getProjectId(); console.log(projectId); // "my-drupal-project" or null if not set ``` #### `getInstanceByUrl(url)` Get instance name (projectId) by URL and set it as the current project. ```javascript const projectId = await sdk.getInstanceByUrl('https://my-drupal-project.ddev.site'); console.log(projectId); // "my-drupal-project" // The projectId is now set on the SDK instance ``` ### Convenience Methods The SDK also provides convenience methods for common operations: #### `getDrupalStatus(projectId?)` Get Drupal status using drush. ```javascript const status = await sdk.getDrupalStatus('my-drupal-project'); // or with instance projectId: await sdk.getDrupalStatus(); ``` #### `clearDrupalCache(projectId?)` Clear Drupal cache using drush. ```javascript const result = await sdk.clearDrupalCache('my-drupal-project'); // or with instance projectId: await sdk.clearDrupalCache(); ``` #### `listFiles(path, projectId?)` List files in the project directory. ```javascript const files = await sdk.listFiles('/var/www/html', 'my-drupal-project'); // or with instance projectId: await sdk.listFiles('/var/www/html'); ``` #### `getUserInfo(uid, projectId?)` Get user information from database. ```javascript const user = await sdk.getUserInfo(1, 'my-drupal-project'); // or with instance projectId: await sdk.getUserInfo(1); ``` #### `getInstalledModules(projectId?)` Get list of installed modules. ```javascript const modules = await sdk.getInstalledModules('my-drupal-project'); // or with instance projectId: await sdk.getInstalledModules(); ``` #### `createNode(title, type, projectId?)` Create a new node. ```javascript const result = await sdk.createNode('My New Article', 'article', 'my-drupal-project'); // or with instance projectId: await sdk.createNode('My New Article', 'article'); ``` #### `getPhpInfo(projectId?)` Get PHP information. ```javascript const phpInfo = await sdk.getPhpInfo('my-drupal-project'); // or with instance projectId: await sdk.getPhpInfo(); ``` ## Error Handling The SDK provides comprehensive error handling with detailed error information from the API gateway. All errors are enhanced with additional context to help you debug issues effectively. ### Basic Error Handling ```javascript try { const result = await sdk.runPhpScript('echo "Hello";'); console.log('Success:', result); } catch (error) { console.error('Error:', error.message); } ``` ### Enhanced Error Information The SDK automatically extracts detailed error information from API responses and provides it in a structured format: ```javascript try { const result = await sdk.runPhpScript(` <?php // This will cause a syntax error if (true { echo "Missing closing brace"; } `); } catch (error) { console.log('Error Message:', error.message); console.log('Status Code:', error.statusCode); // Check if it's an API error with details if (DdevUpdateManagerSDK.isApiError(error)) { const details = DdevUpdateManagerSDK.getErrorDetails(error); console.log('Error Details:', details); // Access specific error information console.log('Short Message:', details.shortMessage); console.log('STDERR:', details.stderr); console.log('STDOUT:', details.stdout); console.log('Command:', details.command); console.log('Exit Code:', details.exitCode); } } ``` ### Error Types and Handling #### API Errors (HTTP 4xx/5xx) These errors contain detailed information from the API gateway: ```javascript try { await sdk.execCommand('invalid-command'); } catch (error) { if (DdevUpdateManagerSDK.isApiError(error)) { const details = DdevUpdateManagerSDK.getErrorDetails(error); // Handle specific error types switch (error.statusCode) { case 401: console.log('Authentication failed - check your API key'); break; case 404: console.log('Project not found - check the project ID'); break; case 500: console.log('Server error - check the API gateway logs'); if (details.stderr) { console.log('Error output:', details.stderr); } break; } // Handle command-specific errors if (details.exitCode === 1) { console.log('Command failed with exit code 1 - likely a syntax or runtime error'); } else if (details.exitCode === 127) { console.log('Command not found - check if the command exists in the DDEV environment'); } } } ``` #### Network/Connection Errors These occur when the SDK can't reach the API gateway: ```javascript try { await sdk.getHello(); } catch (error) { if (!DdevUpdateManagerSDK.isApiError(error)) { console.log('Network/connection error:', error.message); // This could be a timeout, DNS resolution failure, etc. } } ``` ### Error Handling Utilities The SDK provides static utility methods for working with errors: #### `DdevUpdateManagerSDK.isApiError(error)` Check if an error is an API error with details: ```javascript if (DdevUpdateManagerSDK.isApiError(error)) { // This error has API details (statusCode, errorDetails, etc.) const details = DdevUpdateManagerSDK.getErrorDetails(error); } ``` #### `DdevUpdateManagerSDK.getErrorDetails(error)` Extract error details from an API error: ```javascript const details = DdevUpdateManagerSDK.getErrorDetails(error); if (details) { console.log('Command:', details.command); console.log('Exit Code:', details.exitCode); console.log('STDERR:', details.stderr); } ``` #### `DdevUpdateManagerSDK.getOriginalError(error)` Get the original error from an enhanced API error: ```javascript const originalError = DdevUpdateManagerSDK.getOriginalError(error); if (originalError) { console.log('Original error:', originalError.message); } ``` ### Common Error Scenarios #### PHP Syntax Errors ```javascript try { await sdk.runPhpScript(` <?php if (true { // Missing closing brace echo "Hello"; } `); } catch (error) { if (DdevUpdateManagerSDK.isApiError(error)) { const details = DdevUpdateManagerSDK.getErrorDetails(error); console.log('PHP Syntax Error:', details.stderr); // Output: Parse error: syntax error, unexpected token "{" in Standard input code on line 2 } } ``` #### Command Not Found ```javascript try { await sdk.execCommand('non-existent-command'); } catch (error) { if (DdevUpdateManagerSDK.isApiError(error)) { const details = DdevUpdateManagerSDK.getErrorDetails(error); if (details.exitCode === 127) { console.log('Command not found:', details.command); } } } ``` #### Invalid Project ID ```javascript try { await sdk.getProject('non-existent-project'); } catch (error) { if (error.statusCode === 404) { console.log('Project not found - check the project ID'); } } ``` #### Authentication Errors ```javascript try { await sdk.listInstances(); } catch (error) { if (error.statusCode === 401) { console.log('Authentication failed - check your API key'); } } ``` ### Best Practices #### 1. Always Use Try-Catch ```javascript async function safeOperation() { try { return await sdk.execCommand('drush status'); } catch (error) { console.error('Operation failed:', error.message); throw error; // Re-throw if you want to handle it at a higher level } } ``` #### 2. Check Error Types ```javascript async function handleErrors(error) { if (DdevUpdateManagerSDK.isApiError(error)) { // Handle API errors with details const details = DdevUpdateManagerSDK.getErrorDetails(error); console.log('API Error:', details.shortMessage); } else { // Handle network/connection errors console.log('Network Error:', error.message); } } ``` #### 3. Log Error Details for Debugging ```javascript try { await sdk.runPhpScript(complexPhpCode); } catch (error) { console.error('Error:', error.message); if (DdevUpdateManagerSDK.isApiError(error)) { const details = DdevUpdateManagerSDK.getErrorDetails(error); console.error('Full error details:', JSON.stringify(details, null, 2)); } } ``` #### 4. Create Error Handling Wrappers ```javascript async function safeExecute(operation, projectId) { try { return await operation(projectId); } catch (error) { if (DdevUpdateManagerSDK.isApiError(error)) { const details = DdevUpdateManagerSDK.getErrorDetails(error); // Handle specific error types if (details.exitCode === 1) { console.log('Command failed with exit code 1 - likely a syntax or runtime error'); if (details.stderr) { console.log('Error output:', details.stderr); } } } throw error; } } // Usage try { await safeExecute(sdk.getDrupalStatus.bind(sdk), 'my-project'); } catch (error) { console.log('Error handled by wrapper'); } ``` ### Error Handling with Template Literals The template literal methods also benefit from the same error handling: ```javascript try { await sdk.$php` <?php echo "Hello from PHP!"; // This will cause a syntax error if (true { echo "Missing closing brace"; } `; } catch (error) { if (DdevUpdateManagerSDK.isApiError(error)) { const details = DdevUpdateManagerSDK.getErrorDetails(error); console.log('PHP syntax error in template literal:', details.stderr); } } ``` For more examples, see the `error-handling-example.js` file in this package. ## Examples ### Complete Workflow Example with Project ID ```javascript const DdevUpdateManagerSDK = require('api-gateway-sdk'); async function manageDdevProject() { // Set project ID in constructor const sdk = new DdevUpdateManagerSDK({ apiKey: 'your-api-key', baseUrl: 'http://localhost:3000', projectId: 'my-drupal-project' }); try { // List all instances const instances = await sdk.listInstances(); console.log('Available instances:', instances); // Get project details (no projectId needed) const project = await sdk.getProject(); console.log('Project details:', project); // Start project if not running (no projectId needed) if (project.status !== 'running') { await sdk.startProject(); console.log('Project started'); } // Get Drupal status (no projectId needed) const status = await sdk.getDrupalStatus(); console.log('Drupal status:', status); // Clear cache (no projectId needed) await sdk.clearDrupalCache(); console.log('Cache cleared'); // Get installed modules (no projectId needed) const modules = await sdk.getInstalledModules(); console.log('Installed modules:', modules); } catch (error) { console.error('Error:', error.message); } } manageDdevProject(); ``` ### Template Literal Aliases Example ```javascript async function templateLiteralExample() { const sdk = new DdevUpdateManagerSDK({ apiKey: 'your-api-key', projectId: 'my-drupal-project' }); // Execute commands with template literals const files = await sdk.$exec`ls -la`; console.log('Files:', files.stdout); // SQL queries with template literals const users = await sdk.$sql`SELECT uid, name FROM users_field_data WHERE uid = 1`; console.log('Users:', users.stdout); // PHP scripts with template literals const result = await sdk.$php` $term_storage = \Drupal::entityTypeManager()->getStorage('taxonomy_term'); $terms = $term_storage->loadByProperties(['vid' => 'tags']); $count = count($terms); echo "Found {$count} taxonomy terms"; `; console.log('PHP result:', result.stdout); } ``` ### Project ID by URL Example ```javascript async function projectByUrlExample() { const sdk = new DdevUpdateManagerSDK({ apiKey: 'your-api-key' }); // Set project ID by URL const projectId = await sdk.getInstanceByUrl('https://my-drupal-project.ddev.site'); console.log('Project ID:', projectId); // "my-drupal-project" // Now use methods without projectId const status = await sdk.getDrupalStatus(); const cache = await sdk.clearDrupalCache(); // Use template literals const files = await sdk.$exec`pwd`; const users = await sdk.$sql`SELECT COUNT(*) as count FROM users_field_data`; } ``` ### Database Operations Example ```javascript async function databaseOperations() { const sdk = new DdevUpdateManagerSDK({ apiKey: 'your-api-key', projectId: 'my-drupal-project' }); // Get user information (no projectId needed) const user = await sdk.getUserInfo(1); console.log('Admin user:', user); // Custom SQL query with template literal const result = await sdk.$sql` SELECT nid, title, created FROM node_field_data WHERE type = 'article' ORDER BY created DESC LIMIT 5 `; console.log('Recent articles:', result); } ``` ### PHP Script Execution Example ```javascript async function phpOperations() { const sdk = new DdevUpdateManagerSDK({ apiKey: 'your-api-key', projectId: 'my-drupal-project' }); // Create a new vocabulary with template literal const vocabResult = await sdk.$php` $vocabulary = \Drupal\taxonomy\Entity\Vocabulary::create([ 'name' => 'Test Vocabulary', 'vid' => 'test_vocabulary' ]); $vocabulary->save(); echo "Vocabulary created: " . $vocabulary->id(); `; console.log('Vocabulary creation result:', vocabResult); // Get node count with template literal const countResult = await sdk.$php` $query = \Drupal::entityQuery('node')->accessCheck(FALSE); $count = $query->count()->execute(); echo "Total nodes: " . $count; `; console.log('Node count result:', countResult); } ``` ## TypeScript Support The SDK is written in JavaScript but can be used with TypeScript. You can create type definitions for better IntelliSense: ```typescript interface DdevInstance { name: string; status: string; type: string; httpurl: string; } interface ProjectDetails extends DdevInstance { php_version: string; database_type: string; database_version: string; } interface CommandResult { stdout: string; stderr: string; exitCode: number; } interface SqlResult { stdout: { rows: any[]; hasData: boolean; executionTime: string; affectedItemsCount: number; warningsCount: number; warnings: string[]; info: string; autoIncrementValue: number; }; stderr: string; exitCode: number; } ``` ## License ISC