# Server Features This guide covers OSW Studio's advanced server features, available in **Server Mode** only. ## Overview Server Mode unlocks powerful backend capabilities for your published sites, including serverless API endpoints, database management, and secure secrets storage. **Key Features:** - **Edge Functions** - REST API endpoints with JavaScript runtime - **Database** - Per-site SQLite with SQL editor and schema browser - **Server Functions** - Reusable helper code for edge functions - **Scheduled Functions** - Run edge functions on cron schedules - **Secrets** - Encrypted storage for API keys and tokens - **Logs** - Execution history and debugging - **AI Integration** - AI awareness of server features via `/.server/` folder ## Prerequisites - OSW Studio running in **Server Mode** - A published site with `databaseEnabled: true` - Admin access to the site ## Accessing Server Settings 1. Open the **Admin Dashboard** (`/admin`) 2. Navigate to **Sites** and select your site 3. Click the **Server Settings** button (server icon) next to Site Settings - The server icon only appears for published sites with database enabled - You can also access it via the "..." dropdown menu → "Server Settings" The Server Settings modal contains seven tabs: - **Schema** - Browse tables and columns - **SQL** - Execute raw SQL queries - **Functions** - Create and manage edge functions (HTTP endpoints) - **Helpers** - Create and manage server functions (reusable code) - **Secrets** - Store encrypted API keys and tokens - **Schedules** - Create and manage scheduled functions (cron jobs) - **Logs** - View function execution history --- ## Edge Functions ### Creating a Function 1. Go to **Server Settings → Functions** 2. Click **New Function** 3. Configure: - **Name**: Lowercase letters, numbers, and hyphens (e.g., `get-users`) - **HTTP Method**: GET, POST, PUT, DELETE, or ANY - **Description**: Optional description - **Timeout**: 1-30 seconds (default: 5s) - **Code**: JavaScript function body 4. Click **Create Function** ### Function URL Each function is accessible at: ``` https://your-server.com/api/sites/{siteId}/functions/{function-name} ``` For example: ``` https://oswstudio.com/api/sites/abc123/functions/get-products ``` ### Calling Edge Functions from Published Sites Published sites automatically route edge function calls! Your frontend JavaScript can call functions using simple paths: ```javascript // In your published site's JavaScript const response = await fetch('/submit-contact', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name: 'John', email: 'john@example.com' }) }); const result = await response.json(); ``` This works because OSW Studio injects a lightweight interceptor script (~1.5KB) into published HTML files that: - Detects requests that look like edge function calls (paths without file extensions) - Routes them to `/api/sites/{siteId}/functions/{path}` - Works with `fetch()`, `XMLHttpRequest`, and form submissions **Form submissions** are also intercepted: ```html
``` The form data is automatically converted to JSON and sent to your edge function. **Custom event handling:** ```javascript // Listen for edge function responses document.addEventListener('edge-function-response', (e) => { console.log('Result:', e.detail.result); }); document.addEventListener('edge-function-error', (e) => { console.error('Error:', e.detail.error); }); ``` ### Available APIs Your function code has access to these global objects: #### `request` Object ```javascript request.method // HTTP method (GET, POST, etc.) request.body // Parsed JSON body (POST/PUT/PATCH) request.query // Query string parameters request.headers // Request headers request.path // URL path after function name request.params // Path parameters (if any) ``` #### `db` Object (Database) ```javascript // Execute SELECT queries const users = db.query('SELECT * FROM users WHERE active = ?', [true]); const user = db.all('SELECT * FROM users LIMIT 10'); // alias for query // Execute INSERT/UPDATE/DELETE const result = db.run('INSERT INTO users (name, email) VALUES (?, ?)', ['John', 'john@example.com']); // result = { changes: 1 } ``` #### `Response` Object ```javascript // Return JSON Response.json({ users: [...] }); Response.json({ error: 'Not found' }, 404); // Return plain text Response.text('Hello World'); Response.text('Created', 201); // Return error Response.error('Something went wrong', 500); Response.error('Unauthorized', 401); ``` #### `fetch` Function ```javascript // Make external HTTP requests const response = await fetch('https://api.example.com/data'); const data = await response.json(); Response.json(data); // With options const res = await fetch('https://api.example.com/users', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: { name: 'John', email: 'john@example.com' } }); ``` **Security Limits:** - Max 10 requests per function execution - 10 second timeout per request - 5MB max response body - Only `http://` and `https://` protocols allowed - Private IPs blocked in production (localhost, 10.x.x.x, 172.16-31.x.x, 192.168.x.x, 169.254.x.x) - Development mode allows local requests for testing #### `atob` / `btoa` Functions ```javascript // Base64 encode const encoded = btoa('Hello World'); // "SGVsbG8gV29ybGQ=" // Base64 decode const decoded = atob('SGVsbG8gV29ybGQ='); // "Hello World" ``` #### `server` Object (Helper Functions) ```javascript // Call server functions (helpers) defined in the Helpers tab const auth = server.validateAuth(request.headers['x-api-key']); const formatted = server.formatPrice(29.99, 'USD'); const user = server.getUserById(123); ``` See [Server Functions (Helpers)](#server-functions-helpers) for more details. #### `secrets` Object (Encrypted Secrets) ```javascript // Get secret value by name const apiKey = secrets.get('STRIPE_API_KEY'); if (!apiKey) { Response.error('Stripe not configured', 500); return; } // Check if secret exists if (secrets.has('SENDGRID_KEY')) { // Use SendGrid } // List all available secret names const allSecrets = secrets.list(); // ['STRIPE_API_KEY', 'SENDGRID_KEY', ...] ``` See [Secrets](#secrets) for more details. ### Example Functions #### List Items (GET) ```javascript // GET /api/sites/{siteId}/functions/list-items const items = db.query('SELECT * FROM items ORDER BY created_at DESC LIMIT 20'); Response.json({ items }); ``` #### Create Item (POST) ```javascript // POST /api/sites/{siteId}/functions/create-item if (!request.body.name) { Response.error('Name is required', 400); return; } const result = db.run( 'INSERT INTO items (name, description) VALUES (?, ?)', [request.body.name, request.body.description || ''] ); Response.json({ id: result.lastInsertRowid, message: 'Item created' }, 201); ``` #### Get Item by ID (GET with path) ```javascript // GET /api/sites/{siteId}/functions/get-item/123 const id = request.path.split('/')[1]; if (!id) { Response.error('ID required', 400); return; } const item = db.query('SELECT * FROM items WHERE id = ?', [id]); if (item.length === 0) { Response.error('Item not found', 404); return; } Response.json(item[0]); ``` #### External API Proxy ```javascript // GET /api/sites/{siteId}/functions/weather?city=London const city = request.query.city || 'New York'; const apiKey = secrets.get('WEATHER_API_KEY'); if (!apiKey) { Response.error('Weather API not configured', 500); return; } const res = await fetch( `https://api.weatherapi.com/v1/current.json?key=${apiKey}&q=${city}` ); const data = await res.json(); Response.json({ city: data.location.name, temp: data.current.temp_c, condition: data.current.condition.text }); ``` ### Security Considerations #### Sandboxed Execution - Functions run in a **QuickJS WebAssembly sandbox** - a completely separate JavaScript engine - True isolation via WASM boundary: no shared memory or access to Node.js internals - Memory limits enforced by WASM (64MB default) - Execution time limits with interrupt handler (configurable 1-30 seconds) - Allowed globals: `JSON`, `Date`, `Math`, `Array`, `Object`, `String`, `Number`, `Boolean`, `RegExp`, `Error`, `Map`, `Set`, `Promise`, `Symbol`, `console` - Network: `fetch` (with security limits - see above) - Utility functions: `parseInt`, `parseFloat`, `isNaN`, `isFinite`, `encodeURIComponent`, `decodeURIComponent`, `encodeURI`, `decodeURI` - Base64: `atob` (decode), `btoa` (encode) - No access to: `require`, `process`, `__dirname`, `Buffer`, file system - `setTimeout`/`setInterval` disabled (prevents runaway execution) #### Database Protection System tables are protected and cannot be accessed: - `site_info` - `files` - `file_tree_nodes` - `pageviews` - `interactions` - `sessions` - `edge_functions` - `function_logs` - `server_functions` - `secrets` Only user-created tables are accessible via the `db` API. #### Query Limits - Maximum 100 database queries per function execution - Timeout enforced (1-30 seconds, configurable) - SQL keywords validated to prevent dangerous operations ### Managing Functions #### Enable/Disable Click the dropdown menu on a function card and select **Enable** or **Disable**. Disabled functions return 404. #### Edit Click **Edit** in the dropdown to modify the function code, method, or timeout. #### Delete Click **Delete** in the dropdown. This cannot be undone. #### Copy URL Click **Copy URL** below the function card to copy the public endpoint URL. --- ## Server Functions (Helpers) Server functions are reusable JavaScript helpers that can be called from your edge functions via the `server` object. They enable code reuse across multiple edge functions. ### Creating a Server Function 1. Go to **Server Settings → Helpers** 2. Click **New Helper** 3. Configure: - **Name**: Valid JavaScript identifier (camelCase or snake_case, e.g., `validateAuth`, `format_price`) - **Description**: Optional description - **Code**: JavaScript function body 4. Click **Create Function** ### How Server Functions Work Server functions receive arguments via an `args` array and have access to `db`, `fetch`, and `console`. They return a value that is passed back to the calling edge function. ```javascript // Server function "validateAuth" const [apiKey] = args; if (!apiKey) { return { valid: false, error: 'No API key provided' }; } const users = db.query('SELECT * FROM users WHERE api_key = ?', [apiKey]); if (users.length === 0) { return { valid: false, error: 'Invalid API key' }; } return { valid: true, user: users[0] }; ``` ### Calling from Edge Functions Server functions are available on the `server` object. Pass arguments as regular function parameters: ```javascript // Edge function code const auth = server.validateAuth(request.headers['x-api-key']); if (!auth.valid) { Response.error(auth.error, 401); return; } // User is authenticated const products = db.query( 'SELECT * FROM products WHERE user_id = ?', [auth.user.id] ); Response.json({ products }); ``` ### Available APIs in Server Functions | API | Description | |-----|-------------| | `args` | Array of arguments passed from edge function | | `db.query()` | Execute SELECT query | | `db.run()` | Execute INSERT/UPDATE/DELETE | | `db.all()` | Alias for query | | `fetch()` | Make external HTTP requests | | `console.log()` | Log messages (visible in function logs) | ### Example Server Functions #### Validate API Key ```javascript // Name: validateAuth const [apiKey] = args; if (!apiKey) return { valid: false }; const users = db.query('SELECT id, name, role FROM users WHERE api_key = ?', [apiKey]); return users.length > 0 ? { valid: true, user: users[0] } : { valid: false }; ``` #### Format Price ```javascript // Name: formatPrice const [amount, currency = 'USD'] = args; const symbols = { USD: '$', EUR: '€', GBP: '£', JPY: '¥' }; const symbol = symbols[currency] || currency + ' '; return symbol + amount.toFixed(2); ``` #### Get User by ID ```javascript // Name: getUserById const [id] = args; if (!id) return null; const users = db.query('SELECT * FROM users WHERE id = ?', [id]); return users.length > 0 ? users[0] : null; ``` #### Check Permission ```javascript // Name: hasPermission const [userId, permission] = args; if (!userId || !permission) return false; const perms = db.query( 'SELECT 1 FROM user_permissions WHERE user_id = ? AND permission = ?', [userId, permission] ); return perms.length > 0; ``` ### Security Notes - Server functions run in the same QuickJS WASM context as the parent edge function - They share the total execution timeout (not additive) - Recursive calls are possible but limited by timeout - The `server_functions` table is protected and cannot be queried - Only enabled server functions are available to edge functions ### Managing Server Functions - **Enable/Disable**: Toggle from the dropdown menu. Disabled functions are not available to edge functions. - **Edit**: Click Edit to modify the code or description - **Delete**: Click Delete to remove (cannot be undone) --- ## Secrets Secrets provide secure, encrypted storage for sensitive values like API keys, tokens, and passwords. Edge functions can access secrets via the `secrets` object without exposing the actual values in your code. ### Prerequisites Before using secrets, you must set the `SECRETS_ENCRYPTION_KEY` environment variable: ```bash # Generate a secure 256-bit key node -e "console.log(require('crypto').randomBytes(32).toString('base64'))" # Add to your environment export SECRETS_ENCRYPTION_KEY="your-generated-key-here" ``` ### Creating a Secret 1. Go to **Server Settings → Secrets** 2. Click **New Secret** 3. Configure: - **Name**: SCREAMING_SNAKE_CASE (e.g., `STRIPE_API_KEY`, `SENDGRID_TOKEN`) - **Value**: The secret value (will be encrypted) - **Description**: Optional description 4. Click **Create Secret** ### Using Secrets in Edge Functions The `secrets` object is available in all edge functions: ```javascript // Get a secret value const apiKey = secrets.get('STRIPE_API_KEY'); // Check if a secret exists if (secrets.has('STRIPE_API_KEY')) { // Use the secret } // List all available secret names (not values) const names = secrets.list(); // ['STRIPE_API_KEY', 'SENDGRID_TOKEN', ...] ``` ### Example: Stripe API Integration ```javascript // POST /api/sites/{siteId}/functions/create-charge const stripeKey = secrets.get('STRIPE_API_KEY'); if (!stripeKey) { Response.error('Stripe not configured', 500); return; } const { amount, currency, source } = request.body; if (!amount || !source) { Response.error('Amount and source are required', 400); return; } const res = await fetch('https://api.stripe.com/v1/charges', { method: 'POST', headers: { 'Authorization': `Bearer ${stripeKey}`, 'Content-Type': 'application/x-www-form-urlencoded', }, body: new URLSearchParams({ amount: String(amount), currency: currency || 'usd', source, }), }); const charge = await res.json(); Response.json({ charge }); ``` ### Secrets API Reference | Method | Description | |--------|-------------| | `secrets.get(name)` | Get secret value, or `null` if not found | | `secrets.has(name)` | Check if secret exists (returns boolean) | | `secrets.list()` | Get array of all secret names (not values) | ### Security Notes - **Encryption**: Secrets are encrypted using AES-256-GCM with unique IVs per secret - **Never logged**: Secret values are never written to logs or exposed in API responses - **Admin-only**: Only authenticated admins can create, view (metadata only), or delete secrets - **Protected table**: The `secrets` table cannot be queried directly from edge functions - **Key management**: The master encryption key must be stored securely as an environment variable ### Managing Secrets - **Edit**: Click Edit in the dropdown to update the value or description (name cannot be changed) - **Delete**: Click Delete to permanently remove (cannot be undone) - **No value display**: Secret values are never displayed after creation for security --- ## Scheduled Functions (Cron Jobs) Scheduled functions run edge functions automatically on a cron schedule. Use them for periodic tasks like database cleanup, report generation, cache warming, or external API syncing. ### Creating a Scheduled Function 1. Go to **Server Settings → Schedules** 2. Click **New Schedule** 3. Configure: - **Name**: Lowercase letters, numbers, and hyphens (e.g., `daily-cleanup`) - **Edge Function**: Select which edge function to invoke - **Cron Expression**: Standard 5-field cron syntax (e.g., `0 8 * * *`) - **Timezone**: IANA timezone (default: `UTC`) - **Description**: Optional description - **Config**: Optional JSON object passed as the request body 4. Click **Create Schedule** ### How It Works When a scheduled function fires: 1. The cron scheduler triggers at the specified time 2. The linked edge function is invoked with the `config` object as `request.body` 3. The execution result (success/error) and duration are recorded 4. The next run time is calculated from the cron expression The edge function runs in the same QuickJS sandbox as HTTP-triggered invocations, with full access to `db`, `fetch`, `secrets`, `server`, and `console`. ### Cron Expression Reference Cron expressions use 5 fields: `minute hour day-of-month month day-of-week` **Minimum interval: 5 minutes.** Expressions that resolve to intervals shorter than 5 minutes will be rejected. | Expression | Description | |------------|-------------| | `*/5 * * * *` | Every 5 minutes | | `0 * * * *` | Every hour (at minute 0) | | `0 8 * * *` | Daily at 8:00 AM | | `0 0 * * *` | Daily at midnight | | `30 9 * * 1-5` | Weekdays at 9:30 AM | | `0 0 * * 1` | Every Monday at midnight | | `0 0 1 * *` | First of every month at midnight | | `0 0 1 1 *` | January 1st at midnight (yearly) | **Field ranges:** - Minute: 0-59 - Hour: 0-23 - Day of month: 1-31 - Month: 1-12 - Day of week: 0-7 (0 and 7 = Sunday) ### Example Scheduled Functions #### Daily Database Cleanup Clean up old records every day at 3:00 AM UTC: - **Edge Function** (`cleanup`): ```javascript const daysToKeep = request.body.daysToKeep || 30; const cutoff = new Date(Date.now() - daysToKeep * 86400000).toISOString(); const result = db.run('DELETE FROM logs WHERE created_at < ?', [cutoff]); Response.json({ deleted: result.changes, cutoff }); ``` - **Schedule config**: - Cron: `0 3 * * *` - Config: `{ "daysToKeep": 30 }` #### Hourly Stats Aggregation Aggregate analytics data every hour: - **Edge Function** (`aggregate-stats`): ```javascript const hourAgo = new Date(Date.now() - 3600000).toISOString(); const stats = db.query('SELECT COUNT(*) as views FROM pageviews WHERE timestamp > ?', [hourAgo]); db.run('INSERT INTO hourly_stats (hour, views) VALUES (?, ?)', [new Date().toISOString().slice(0, 13), stats[0].views]); Response.json({ aggregated: true, views: stats[0].views }); ``` - **Schedule config**: - Cron: `0 * * * *` - Config: `{}` #### Weekly Report Email Send a weekly summary every Monday at 9:00 AM: - **Edge Function** (`send-weekly-report`): ```javascript const apiKey = secrets.get('SENDGRID_KEY'); if (!apiKey) { Response.error('Email not configured', 500); return; } const stats = db.query('SELECT COUNT(*) as total FROM orders WHERE created_at > datetime("now", "-7 days")'); const res = await fetch('https://api.sendgrid.com/v3/mail/send', { method: 'POST', headers: { 'Authorization': 'Bearer ' + apiKey, 'Content-Type': 'application/json' }, body: JSON.stringify({ personalizations: [{ to: [{ email: request.body.recipient }] }], from: { email: 'reports@example.com' }, subject: 'Weekly Report', content: [{ type: 'text/plain', value: 'Orders this week: ' + stats[0].total }] }) }); Response.json({ sent: res.ok }); ``` - **Schedule config**: - Cron: `0 9 * * 1` - Timezone: `America/New_York` - Config: `{ "recipient": "admin@example.com" }` ### Managing Scheduled Functions - **Enable/Disable**: Toggle from the dropdown menu. Disabled schedules won't fire. - **Edit**: Click Edit to modify the cron expression, linked function, timezone, or config. - **Delete**: Click Delete to remove (cannot be undone). - **Status tracking**: Each schedule card shows the next run time, last run status (success/error), and last run time. --- ## SQL Editor The SQL Editor allows direct SQL query execution against your site's database. ### Executing Queries 1. Go to **Server Settings → SQL** 2. Type your SQL query in the editor 3. Click **Execute** or press `Ctrl/Cmd + Enter` 4. View results in the table below ### Query History The editor maintains a history of your last 20 queries (stored in browser localStorage). Click **History** to view and re-run previous queries. ### Supported Operations ```sql -- SELECT queries SELECT * FROM products WHERE price > 100; SELECT COUNT(*) FROM orders; -- INSERT INSERT INTO products (name, price) VALUES ('Widget', 29.99); -- UPDATE UPDATE products SET price = 24.99 WHERE id = 1; -- DELETE DELETE FROM products WHERE discontinued = 1; -- CREATE TABLE CREATE TABLE products ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, price REAL DEFAULT 0, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); -- ALTER TABLE ALTER TABLE products ADD COLUMN stock INTEGER DEFAULT 0; -- DROP TABLE (use with caution!) DROP TABLE old_products; ``` ### Query Safety - System tables are accessible (read-only for some operations) - All queries are executed with full permissions - use caution! - No automatic transaction management - consider wrapping related operations --- ## Schema Viewer The Schema Viewer displays your database structure. ### Viewing Tables 1. Go to **Server Settings → Schema** 2. Click on a table name to expand and view columns 3. Each column shows: name, type, nullable, default value, primary key status ### System Tables Toggle **Show System Tables** to view OSW Studio's internal tables: - `site_info` - Site metadata - `files` - File contents - `file_tree_nodes` - File tree structure - `pageviews` / `interactions` / `sessions` - Analytics data - `edge_functions` - Function definitions - `function_logs` - Execution logs System tables are marked with a "(system)" label. --- ## Execution Logs View function execution history in the **Logs** tab. ### Log Information Each log entry shows: - **Status**: Success (2xx), redirect (3xx), or error (4xx/5xx) - **Function**: Function name - **Method**: HTTP method used - **Path**: Request path - **Duration**: Execution time in milliseconds - **Time**: Timestamp ### Managing Logs - Click **Refresh** to load latest logs - Click **Clear** to delete all logs (cannot be undone) - Logs are limited to the most recent 200 entries --- ## Best Practices ### Function Design 1. **Keep functions focused** - One function per operation 2. **Validate input** - Check `request.body` and `request.query` before use 3. **Handle errors** - Return appropriate HTTP status codes 4. **Use meaningful names** - `create-order` not `func1` ### Database Usage 1. **Use parameterized queries** - Prevents SQL injection ```javascript // Good db.query('SELECT * FROM users WHERE id = ?', [userId]); // Bad - SQL injection risk! db.query(`SELECT * FROM users WHERE id = ${userId}`); ``` 2. **Create indexes** for frequently queried columns: ```sql CREATE INDEX idx_orders_user_id ON orders(user_id); ``` 3. **Limit result sets** to avoid memory issues: ```javascript db.query('SELECT * FROM products LIMIT 100'); ``` ### Performance 1. **Set appropriate timeouts** - Don't use 30s if 5s is sufficient 2. **Minimize external requests** - Each `fetch()` adds latency 3. **Cache when possible** - Store API responses in your database --- ## Troubleshooting ### Function Returns 404 - Check that the function is **enabled** - Verify the function name in the URL is correct - Ensure the site is published and has database enabled ### Function Returns 500 - Check the **Logs** tab for error details - Verify your SQL queries are valid - Ensure external APIs are responding ### Query Execution Failed - Check SQL syntax - Verify table and column names - Look for constraint violations (unique, foreign key) ### Cannot Access Table - System tables are protected in edge functions - Use the SQL Editor for system table access --- ## API Reference ### Public Endpoints | Method | URL | Description | |--------|-----|-------------| | * | `/api/sites/{siteId}/functions/{name}` | Invoke edge function | | * | `/api/sites/{siteId}/functions/{name}/*` | Invoke with path params | ### Admin Endpoints (Requires Authentication) | Method | URL | Description | |--------|-----|-------------| | GET | `/api/admin/sites/{siteId}/functions` | List edge functions | | POST | `/api/admin/sites/{siteId}/functions` | Create edge function | | GET | `/api/admin/sites/{siteId}/functions/{id}` | Get edge function | | PUT | `/api/admin/sites/{siteId}/functions/{id}` | Update edge function | | DELETE | `/api/admin/sites/{siteId}/functions/{id}` | Delete edge function | | GET | `/api/admin/sites/{siteId}/server-functions` | List server functions | | POST | `/api/admin/sites/{siteId}/server-functions` | Create server function | | GET | `/api/admin/sites/{siteId}/server-functions/{id}` | Get server function | | PUT | `/api/admin/sites/{siteId}/server-functions/{id}` | Update server function | | DELETE | `/api/admin/sites/{siteId}/server-functions/{id}` | Delete server function | | GET | `/api/admin/sites/{siteId}/scheduled-functions` | List scheduled functions | | POST | `/api/admin/sites/{siteId}/scheduled-functions` | Create scheduled function | | GET | `/api/admin/sites/{siteId}/scheduled-functions/{id}` | Get scheduled function | | PUT | `/api/admin/sites/{siteId}/scheduled-functions/{id}` | Update scheduled function | | DELETE | `/api/admin/sites/{siteId}/scheduled-functions/{id}` | Delete scheduled function | | GET | `/api/admin/sites/{siteId}/secrets` | List secrets (metadata only) | | POST | `/api/admin/sites/{siteId}/secrets` | Create secret | | GET | `/api/admin/sites/{siteId}/secrets/{id}` | Get secret (metadata only) | | PUT | `/api/admin/sites/{siteId}/secrets/{id}` | Update secret | | DELETE | `/api/admin/sites/{siteId}/secrets/{id}` | Delete secret | | GET | `/api/admin/sites/{siteId}/database/schema` | Get schema | | POST | `/api/admin/sites/{siteId}/database/query` | Execute SQL | | GET | `/api/admin/sites/{siteId}/database/logs` | Get logs | | DELETE | `/api/admin/sites/{siteId}/database/logs` | Clear logs | --- ## AI Integration OSW Studio's AI assistant can understand and work with your server features when you select a site in the workspace. ### How It Works 1. **Select a Site** - Use the site selector dropdown in the workspace header 2. **Server Context Loaded** - OSW Studio fetches the site's server features 3. **AI Awareness** - The AI receives information about available: - Edge functions (endpoints, methods) - Database schema (tables, columns) - Server functions (helpers) - Scheduled functions (cron schedules) - Secrets (names only, not values) ### The `/.server/` Folder When a site is selected, a hidden `/.server/` folder appears in the file explorer containing: | Folder | Contents | |--------|----------| | `edge-functions/*.json` | Edge function endpoints | | `server-functions/*.json` | Helper functions | | `scheduled-functions/*.json` | Cron schedules | | `secrets/*.json` | Secret names (not values) | | `db/schema.sql` | Database schema | These files are **read-only** and **transient** - they reflect the current site's state but are not saved with the project. ### Using AI with Server Features **Example prompts:** ``` What tables are in the database? ``` ``` Create an edge function to list all products ``` ``` I need an endpoint that validates API keys using the STRIPE_KEY secret ``` ``` Create a scheduled function to clean up old records every night ``` ``` Help me design a schema for a blog with posts and comments ``` The AI can: - Read and explain your current schema - Suggest edge function implementations - Reference available secrets by name - Help design database structures - Debug function issues ### Viewing Hidden Files To see the `/.server/` folder: 1. Right-click in the File Explorer 2. Select **Show Hidden Files** 3. Look for the folder with the orange server icon See also: **[Server Mode → Server Context Integration](?doc=server-mode#server-context-integration)**