File size: 29,402 Bytes
03eeded
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
# 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

## Prerequisites

- OSW Studio running in **Server Mode**
- A published deployment with `databaseEnabled: true`
- Admin access to the deployment

## Accessing Server Settings

1. Open the **Admin Dashboard** (`/admin`)
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://oswstudio.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/admin/deployments/{deploymentId}/functions` | List edge functions |
| POST | `/api/admin/deployments/{deploymentId}/functions` | Create edge function |
| GET | `/api/admin/deployments/{deploymentId}/functions/{id}` | Get edge function |
| PUT | `/api/admin/deployments/{deploymentId}/functions/{id}` | Update edge function |
| DELETE | `/api/admin/deployments/{deploymentId}/functions/{id}` | Delete edge function |
| GET | `/api/admin/deployments/{deploymentId}/server-functions` | List server functions |
| POST | `/api/admin/deployments/{deploymentId}/server-functions` | Create server function |
| GET | `/api/admin/deployments/{deploymentId}/server-functions/{id}` | Get server function |
| PUT | `/api/admin/deployments/{deploymentId}/server-functions/{id}` | Update server function |
| DELETE | `/api/admin/deployments/{deploymentId}/server-functions/{id}` | Delete server function |
| GET | `/api/admin/deployments/{deploymentId}/scheduled-functions` | List scheduled functions |
| POST | `/api/admin/deployments/{deploymentId}/scheduled-functions` | Create scheduled function |
| GET | `/api/admin/deployments/{deploymentId}/scheduled-functions/{id}` | Get scheduled function |
| PUT | `/api/admin/deployments/{deploymentId}/scheduled-functions/{id}` | Update scheduled function |
| DELETE | `/api/admin/deployments/{deploymentId}/scheduled-functions/{id}` | Delete scheduled function |
| GET | `/api/admin/deployments/{deploymentId}/secrets` | List secrets (metadata only) |
| POST | `/api/admin/deployments/{deploymentId}/secrets` | Create secret |
| GET | `/api/admin/deployments/{deploymentId}/secrets/{id}` | Get secret (metadata only) |
| PUT | `/api/admin/deployments/{deploymentId}/secrets/{id}` | Update secret |
| DELETE | `/api/admin/deployments/{deploymentId}/secrets/{id}` | Delete secret |
| GET | `/api/admin/deployments/{deploymentId}/database/schema` | Get schema |
| POST | `/api/admin/deployments/{deploymentId}/database/query` | Execute SQL |
| GET | `/api/admin/deployments/{deploymentId}/database/logs` | Get logs |
| DELETE | `/api/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. **Select a Deployment** - Use the deployment selector dropdown in the workspace header
2. **Server Context Loaded** - OSW Studio fetches the deployment's backend 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 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 are **read-only** and **transient** - they reflect the current deployment's state but are not saved with the project.

### 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)**