osw-studio / docs /SERVER_FEATURES.md
otst's picture
add scheduled functions (cron jobs) with admin UI and scheduler
734c970
# 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
<form action="/submit-contact" method="POST">
<input name="email" type="email" required>
<button type="submit">Subscribe</button>
</form>
```
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)**