| # Backend Features |
|
|
| This guide covers OSW Studio's advanced backend features, available in **Server Mode** only. |
|
|
| ## Overview |
|
|
| Server Mode unlocks powerful backend capabilities for your published deployments, including serverless API endpoints, database management, and secure secrets storage. |
|
|
| **Key Features:** |
| - **Edge Functions** - REST API endpoints with JavaScript runtime |
| - **Database** - Per-deployment 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 backend features via `/.server/` folder |
|
|
| ## Where Backend Features Live |
|
|
| Backend features are **project-scoped** — they belong to the project, not to a specific deployment. When you publish the project, its backend features are extracted into the deployment's runtime database. The same project can be published multiple times; each deployment gets a snapshot of the project's backend features at publish time. |
|
|
| You can manage backend features in two places: |
|
|
| 1. **Project Settings** (primary, in the workspace): Click **Project Settings** in the workspace header to open a modal with Schema, Functions, Helpers, Secrets, and Schedules tabs. Changes take effect in the project and will be included in the next publish. |
| 2. **Deployment Server Settings** (for an already-published deployment): Open the deployment in the Admin Dashboard to inspect and tweak the runtime copy of its backend. This is useful for rotating secrets or debugging a published deployment without re-publishing from the project. |
|
|
| ## Prerequisites |
|
|
| - OSW Studio running in **Server Mode** |
| - A project with Backend enabled (toggle in Project Settings) |
|
|
| ## Accessing Server Settings |
|
|
| **From the workspace (recommended):** |
| 1. Open your project |
| 2. Click **Project Settings** in the workspace header |
| 3. Toggle **Backend Enabled** if it isn't already |
| 4. Use the tabs to create edge functions, server functions, secrets, and schedules |
|
|
| **From the Admin Dashboard (for published deployments):** |
| 1. Open the **Admin Dashboard** (`/admin`) or `/w/{workspaceId}/deployments` |
| 2. Navigate to **Deployments** and select your deployment |
| 3. Click the **Server Settings** button (server icon) next to Deployment Settings |
| - The server icon only appears for published deployments 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/deployments/{deploymentId}/functions/{function-name} |
| ``` |
|
|
| For example: |
| ``` |
| https://your-instance.com/api/deployments/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/deployments/{deploymentId}/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/deployments/{deploymentId}/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/deployments/{deploymentId}/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/deployments/{deploymentId}/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/deployments/{deploymentId}/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/deployments/{deploymentId}/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 deployment'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` - Deployment 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 deployment 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/deployments/{deploymentId}/functions/{name}` | Invoke edge function | |
| | * | `/api/deployments/{deploymentId}/functions/{name}/*` | Invoke with path params | |
|
|
| ### Admin Endpoints (Requires Authentication) |
|
|
| | Method | URL | Description | |
| |--------|-----|-------------| |
| | GET | `/api/w/{workspaceId}/admin/deployments/{deploymentId}/functions` | List edge functions | |
| | POST | `/api/w/{workspaceId}/admin/deployments/{deploymentId}/functions` | Create edge function | |
| | GET | `/api/w/{workspaceId}/admin/deployments/{deploymentId}/functions/{id}` | Get edge function | |
| | PUT | `/api/w/{workspaceId}/admin/deployments/{deploymentId}/functions/{id}` | Update edge function | |
| | DELETE | `/api/w/{workspaceId}/admin/deployments/{deploymentId}/functions/{id}` | Delete edge function | |
| | GET | `/api/w/{workspaceId}/admin/deployments/{deploymentId}/server-functions` | List server functions | |
| | POST | `/api/w/{workspaceId}/admin/deployments/{deploymentId}/server-functions` | Create server function | |
| | GET | `/api/w/{workspaceId}/admin/deployments/{deploymentId}/server-functions/{id}` | Get server function | |
| | PUT | `/api/w/{workspaceId}/admin/deployments/{deploymentId}/server-functions/{id}` | Update server function | |
| | DELETE | `/api/w/{workspaceId}/admin/deployments/{deploymentId}/server-functions/{id}` | Delete server function | |
| | GET | `/api/w/{workspaceId}/admin/deployments/{deploymentId}/scheduled-functions` | List scheduled functions | |
| | POST | `/api/w/{workspaceId}/admin/deployments/{deploymentId}/scheduled-functions` | Create scheduled function | |
| | GET | `/api/w/{workspaceId}/admin/deployments/{deploymentId}/scheduled-functions/{id}` | Get scheduled function | |
| | PUT | `/api/w/{workspaceId}/admin/deployments/{deploymentId}/scheduled-functions/{id}` | Update scheduled function | |
| | DELETE | `/api/w/{workspaceId}/admin/deployments/{deploymentId}/scheduled-functions/{id}` | Delete scheduled function | |
| | GET | `/api/w/{workspaceId}/admin/deployments/{deploymentId}/secrets` | List secrets (metadata only) | |
| | POST | `/api/w/{workspaceId}/admin/deployments/{deploymentId}/secrets` | Create secret | |
| | GET | `/api/w/{workspaceId}/admin/deployments/{deploymentId}/secrets/{id}` | Get secret (metadata only) | |
| | PUT | `/api/w/{workspaceId}/admin/deployments/{deploymentId}/secrets/{id}` | Update secret | |
| | DELETE | `/api/w/{workspaceId}/admin/deployments/{deploymentId}/secrets/{id}` | Delete secret | |
| | GET | `/api/w/{workspaceId}/admin/deployments/{deploymentId}/database/schema` | Get schema | |
| | POST | `/api/w/{workspaceId}/admin/deployments/{deploymentId}/database/query` | Execute SQL | |
| | GET | `/api/w/{workspaceId}/admin/deployments/{deploymentId}/database/logs` | Get logs | |
| | DELETE | `/api/w/{workspaceId}/admin/deployments/{deploymentId}/database/logs` | Clear logs | |
|
|
| --- |
|
|
| ## AI Integration |
|
|
| OSW Studio's AI assistant can understand and work with your backend features when you select a deployment in the workspace. |
|
|
| ### How It Works |
|
|
| 1. **Project Backend Context** - When the project has Backend Enabled, its edge functions, helpers, secrets, schedules, and schema are mounted automatically in `/.server/` |
| 2. **Optional Deployment Overlay** - Selecting a deployment in the workspace header additionally layers in that deployment's runtime state (published functions, live schema, etc.) |
| 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 deployment 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 reflect the project's backend feature state: |
| - **Schema** (`db/schema.sql`) is read-only — edit schema by running SQL in Project Settings or via the AI's `sqlite3` shell command |
| - **Functions, helpers, secrets, and schedules** can be created and edited by the AI using shell commands. Changes update the corresponding records in the project's database |
|
|
| Files are transient in the sense that they're regenerated from the project's backend state — you don't commit them manually. |
|
|
| ### Using AI with Backend 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)** |
|
|