openkbs 0.0.64 → 0.0.66

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/elastic/README.md +1150 -0
  2. package/elastic/functions.md +328 -0
  3. package/elastic/postgres.md +287 -0
  4. package/elastic/pulse.md +386 -0
  5. package/elastic/storage.md +291 -0
  6. package/package.json +1 -1
  7. package/src/actions.js +76 -92
  8. package/src/index.js +23 -10
  9. package/src/utils.js +8 -4
  10. package/templates/.claude/skills/openkbs/SKILL.md +184 -0
  11. package/templates/.claude/skills/openkbs/metadata.json +1 -0
  12. package/templates/.claude/skills/openkbs/reference/backend-sdk.md +428 -0
  13. package/templates/.claude/skills/openkbs/reference/commands.md +370 -0
  14. package/templates/.claude/skills/openkbs/reference/elastic-services.md +327 -0
  15. package/templates/.claude/skills/openkbs/reference/frontend-sdk.md +299 -0
  16. package/version.json +3 -3
  17. package/templates/.openkbs/knowledge/metadata.json +0 -3
  18. package/templates/CLAUDE.md +0 -655
  19. /package/templates/{.openkbs/knowledge → .claude/skills/openkbs}/examples/ai-copywriter-agent/app/icon.png +0 -0
  20. /package/templates/{.openkbs/knowledge → .claude/skills/openkbs}/examples/ai-copywriter-agent/app/instructions.txt +0 -0
  21. /package/templates/{.openkbs/knowledge → .claude/skills/openkbs}/examples/ai-copywriter-agent/app/settings.json +0 -0
  22. /package/templates/{.openkbs/knowledge → .claude/skills/openkbs}/examples/ai-copywriter-agent/scripts/run_job.js +0 -0
  23. /package/templates/{.openkbs/knowledge → .claude/skills/openkbs}/examples/ai-copywriter-agent/scripts/utils/agent_client.js +0 -0
  24. /package/templates/{.openkbs/knowledge → .claude/skills/openkbs}/examples/ai-copywriter-agent/src/Events/actions.js +0 -0
  25. /package/templates/{.openkbs/knowledge → .claude/skills/openkbs}/examples/ai-copywriter-agent/src/Events/handler.js +0 -0
  26. /package/templates/{.openkbs/knowledge → .claude/skills/openkbs}/examples/ai-copywriter-agent/src/Events/onRequest.js +0 -0
  27. /package/templates/{.openkbs/knowledge → .claude/skills/openkbs}/examples/ai-copywriter-agent/src/Events/onRequest.json +0 -0
  28. /package/templates/{.openkbs/knowledge → .claude/skills/openkbs}/examples/ai-copywriter-agent/src/Events/onResponse.js +0 -0
  29. /package/templates/{.openkbs/knowledge → .claude/skills/openkbs}/examples/ai-copywriter-agent/src/Events/onResponse.json +0 -0
  30. /package/templates/{.openkbs/knowledge → .claude/skills/openkbs}/examples/ai-copywriter-agent/src/Frontend/contentRender.js +0 -0
  31. /package/templates/{.openkbs/knowledge → .claude/skills/openkbs}/examples/ai-copywriter-agent/src/Frontend/contentRender.json +0 -0
  32. /package/templates/{.openkbs/knowledge → .claude/skills/openkbs}/examples/ai-marketing-agent/README.md +0 -0
  33. /package/templates/{.openkbs/knowledge → .claude/skills/openkbs}/examples/ai-marketing-agent/app/instructions.txt +0 -0
  34. /package/templates/{.openkbs/knowledge → .claude/skills/openkbs}/examples/ai-marketing-agent/app/settings.json +0 -0
  35. /package/templates/{.openkbs/knowledge → .claude/skills/openkbs}/examples/ai-marketing-agent/src/Events/actions.js +0 -0
  36. /package/templates/{.openkbs/knowledge → .claude/skills/openkbs}/examples/ai-marketing-agent/src/Events/onRequest.js +0 -0
  37. /package/templates/{.openkbs/knowledge → .claude/skills/openkbs}/examples/ai-marketing-agent/src/Events/onRequest.json +0 -0
  38. /package/templates/{.openkbs/knowledge → .claude/skills/openkbs}/examples/ai-marketing-agent/src/Events/onResponse.js +0 -0
  39. /package/templates/{.openkbs/knowledge → .claude/skills/openkbs}/examples/ai-marketing-agent/src/Events/onResponse.json +0 -0
  40. /package/templates/{.openkbs/knowledge → .claude/skills/openkbs}/examples/ai-marketing-agent/src/Frontend/contentRender.js +0 -0
  41. /package/templates/{.openkbs/knowledge → .claude/skills/openkbs}/examples/ai-marketing-agent/src/Frontend/contentRender.json +0 -0
@@ -0,0 +1,1150 @@
1
+ # OpenKBS Elastic Services
2
+
3
+ Deploy full-stack applications with zero infrastructure setup. Elastic Services provide production-ready PostgreSQL, S3 storage, serverless functions, and real-time WebSockets - all with simple CLI commands.
4
+
5
+ ## Table of Contents
6
+
7
+ - [Overview](#overview)
8
+ - [Quick Start](#quick-start)
9
+ - [PostgreSQL Database](#postgresql-database)
10
+ - [S3 Storage & CloudFront CDN](#s3-storage--cloudfront-cdn)
11
+ - [Serverless Functions](#serverless-functions)
12
+ - [Real-time Pulse](#real-time-pulse)
13
+ - [CLI Reference](#cli-reference)
14
+
15
+ ---
16
+
17
+ ## Overview
18
+
19
+ ### What Are Elastic Services?
20
+
21
+ Elastic Services extend your OpenKBS Knowledge Base with cloud infrastructure:
22
+
23
+ | Service | What You Get | Use Case |
24
+ |---------|--------------|----------|
25
+ | **Postgres** | PostgreSQL database (Neon) | Store users, orders, content |
26
+ | **Storage** | S3 bucket + CloudFront CDN | Upload images, files, media |
27
+ | **Functions** | Serverless Lambda functions | Build APIs, webhooks, background jobs |
28
+ | **Pulse** | Real-time WebSocket | Live updates, chat, presence |
29
+
30
+ ### Why Elastic?
31
+
32
+ **Before Elastic:**
33
+ - Set up AWS accounts, IAM roles, VPCs
34
+ - Configure databases, connection strings
35
+ - Manage deployments, CI/CD pipelines
36
+ - Handle SSL certificates, domains
37
+ - Debug networking issues
38
+
39
+ **With Elastic:**
40
+ ```bash
41
+ openkbs postgres enable # Database ready in 10 seconds
42
+ openkbs storage enable # S3 bucket with CDN
43
+ openkbs fn push api # Deploy your API
44
+ openkbs deploy # Ship everything
45
+ ```
46
+
47
+ Your app is live at `https://yourdomain.com/`.
48
+
49
+ ### Environment Variables
50
+
51
+ When you enable Elastic services, environment variables are automatically injected into your functions:
52
+
53
+ | Variable | Service | Example |
54
+ |----------|---------|---------|
55
+ | `DATABASE_URL` | Postgres | `postgresql://user:pass@host/db` |
56
+ | `STORAGE_BUCKET` | Storage | `openkbs-elastic-xyz123` |
57
+ | `STORAGE_REGION` | Storage | `us-east-1` |
58
+ | `OPENKBS_KB_ID` | All | `abc123xyz` |
59
+ | `OPENKBS_API_KEY` | All | Your API key |
60
+
61
+ No manual configuration needed. Just use `process.env.DATABASE_URL` in your code.
62
+
63
+ ### Regions
64
+
65
+ Available in 3 regions:
66
+
67
+ | Region | Location | Latency Target |
68
+ |--------|----------|----------------|
69
+ | `us-east-1` | N. Virginia (default) | Americas |
70
+ | `eu-central-1` | Frankfurt | Europe |
71
+ | `ap-southeast-1` | Singapore | Asia-Pacific |
72
+
73
+ Specify region in `openkbs.json` or with `--region` flag.
74
+
75
+ ---
76
+
77
+ ## Quick Start
78
+
79
+ ### 1. Create Project
80
+
81
+ ```bash
82
+ mkdir my-app && cd my-app
83
+ openkbs init
84
+ ```
85
+
86
+ ### 2. Configure Services
87
+
88
+ Create `openkbs.json`:
89
+
90
+ ```json
91
+ {
92
+ "name": "my-app",
93
+ "region": "us-east-1",
94
+ "elastic": {
95
+ "postgres": true,
96
+ "storage": {
97
+ "cloudfront": "media"
98
+ },
99
+ "pulse": true
100
+ },
101
+ "functions": ["api"],
102
+ "site": "./site"
103
+ }
104
+ ```
105
+
106
+ ### 3. Create API Function
107
+
108
+ Create `functions/api/index.mjs`:
109
+
110
+ ```javascript
111
+ import pg from 'pg';
112
+
113
+ const db = new pg.Client({ connectionString: process.env.DATABASE_URL });
114
+ let connected = false;
115
+
116
+ export async function handler(event) {
117
+ if (!connected) { await db.connect(); connected = true; }
118
+
119
+ const { action } = JSON.parse(event.body || '{}');
120
+
121
+ if (action === 'list') {
122
+ const { rows } = await db.query('SELECT * FROM items');
123
+ return { statusCode: 200, body: JSON.stringify(rows) };
124
+ }
125
+
126
+ return { statusCode: 400, body: JSON.stringify({ error: 'Unknown action' }) };
127
+ }
128
+ ```
129
+
130
+ Create `functions/api/package.json`:
131
+
132
+ ```json
133
+ {
134
+ "type": "module",
135
+ "dependencies": {
136
+ "pg": "^8.11.3"
137
+ }
138
+ }
139
+ ```
140
+
141
+ ### 4. Deploy
142
+
143
+ ```bash
144
+ cd functions/api && npm install && cd ../..
145
+ openkbs deploy
146
+ ```
147
+
148
+ Done. Your API is live at `https://yourdomain.com/api`.
149
+
150
+ ---
151
+
152
+ ## PostgreSQL Database
153
+
154
+ Add a PostgreSQL database to your project with one command. Elastic Postgres uses Neon - a serverless PostgreSQL that scales automatically.
155
+
156
+ ### Enable Postgres
157
+
158
+ ```bash
159
+ openkbs postgres enable
160
+ ```
161
+
162
+ That's it. Your database is ready.
163
+
164
+ ### Check Status
165
+
166
+ ```bash
167
+ openkbs postgres status
168
+ ```
169
+
170
+ Output:
171
+ ```
172
+ Postgres Status:
173
+ Enabled: true
174
+ Host: ep-xyz-123456.us-east-1.aws.neon.tech
175
+ Database: neondb
176
+ Region: us-east-1
177
+ ```
178
+
179
+ ### Get Connection String
180
+
181
+ ```bash
182
+ openkbs postgres connection
183
+ ```
184
+
185
+ Output:
186
+ ```
187
+ postgresql://user:password@ep-xyz-123456.us-east-1.aws.neon.tech/neondb?sslmode=require
188
+ ```
189
+
190
+ ### Use in Node.js
191
+
192
+ ```javascript
193
+ import pg from 'pg';
194
+
195
+ const db = new pg.Client({ connectionString: process.env.DATABASE_URL });
196
+ let connected = false;
197
+
198
+ export async function handler(event) {
199
+ // Connect once, reuse across invocations
200
+ if (!connected) {
201
+ await db.connect();
202
+ connected = true;
203
+
204
+ // Create tables on first run
205
+ await db.query(`
206
+ CREATE TABLE IF NOT EXISTS users (
207
+ id SERIAL PRIMARY KEY,
208
+ name TEXT NOT NULL,
209
+ email TEXT UNIQUE NOT NULL,
210
+ created_at TIMESTAMP DEFAULT NOW()
211
+ )
212
+ `);
213
+ }
214
+
215
+ const { action, ...data } = JSON.parse(event.body || '{}');
216
+
217
+ switch (action) {
218
+ case 'list':
219
+ const { rows } = await db.query('SELECT * FROM users ORDER BY created_at DESC');
220
+ return { statusCode: 200, body: JSON.stringify(rows) };
221
+
222
+ case 'create':
223
+ const result = await db.query(
224
+ 'INSERT INTO users (name, email) VALUES ($1, $2) RETURNING *',
225
+ [data.name, data.email]
226
+ );
227
+ return { statusCode: 200, body: JSON.stringify(result.rows[0]) };
228
+
229
+ case 'delete':
230
+ await db.query('DELETE FROM users WHERE id = $1', [data.id]);
231
+ return { statusCode: 200, body: JSON.stringify({ deleted: true }) };
232
+
233
+ default:
234
+ return { statusCode: 400, body: JSON.stringify({ error: 'Unknown action' }) };
235
+ }
236
+ }
237
+ ```
238
+
239
+ ### Use in Python
240
+
241
+ ```python
242
+ import json
243
+ import os
244
+ import psycopg2
245
+
246
+ conn = None
247
+
248
+ def handler(event, context):
249
+ global conn
250
+ if conn is None:
251
+ conn = psycopg2.connect(os.environ['DATABASE_URL'])
252
+ with conn.cursor() as cur:
253
+ cur.execute('''
254
+ CREATE TABLE IF NOT EXISTS items (
255
+ id SERIAL PRIMARY KEY,
256
+ name TEXT NOT NULL
257
+ )
258
+ ''')
259
+ conn.commit()
260
+
261
+ body = json.loads(event.get('body', '{}'))
262
+ action = body.get('action')
263
+
264
+ with conn.cursor() as cur:
265
+ if action == 'list':
266
+ cur.execute('SELECT * FROM items')
267
+ rows = cur.fetchall()
268
+ return {'statusCode': 200, 'body': json.dumps(rows)}
269
+
270
+ elif action == 'create':
271
+ cur.execute('INSERT INTO items (name) VALUES (%s) RETURNING id', (body['name'],))
272
+ conn.commit()
273
+ return {'statusCode': 200, 'body': json.dumps({'id': cur.fetchone()[0]})}
274
+
275
+ return {'statusCode': 400, 'body': json.dumps({'error': 'Unknown action'})}
276
+ ```
277
+
278
+ ### Use in Java
279
+
280
+ ```java
281
+ package com.example;
282
+
283
+ import com.amazonaws.services.lambda.runtime.Context;
284
+ import com.amazonaws.services.lambda.runtime.RequestHandler;
285
+ import com.google.gson.Gson;
286
+ import java.sql.*;
287
+ import java.util.*;
288
+
289
+ public class Handler implements RequestHandler<Map<String, Object>, Map<String, Object>> {
290
+ private static Connection conn;
291
+ private static final Gson gson = new Gson();
292
+
293
+ @Override
294
+ public Map<String, Object> handleRequest(Map<String, Object> event, Context context) {
295
+ try {
296
+ if (conn == null || conn.isClosed()) {
297
+ conn = DriverManager.getConnection(System.getenv("DATABASE_URL"));
298
+ try (Statement stmt = conn.createStatement()) {
299
+ stmt.execute("CREATE TABLE IF NOT EXISTS items (id SERIAL PRIMARY KEY, name TEXT)");
300
+ }
301
+ }
302
+
303
+ String body = (String) event.get("body");
304
+ Map<String, Object> request = gson.fromJson(body, Map.class);
305
+ String action = (String) request.get("action");
306
+
307
+ if ("list".equals(action)) {
308
+ List<Map<String, Object>> items = new ArrayList<>();
309
+ try (ResultSet rs = conn.createStatement().executeQuery("SELECT * FROM items")) {
310
+ while (rs.next()) {
311
+ items.add(Map.of("id", rs.getInt("id"), "name", rs.getString("name")));
312
+ }
313
+ }
314
+ return Map.of("statusCode", 200, "body", gson.toJson(items));
315
+ }
316
+
317
+ return Map.of("statusCode", 400, "body", "{\"error\":\"Unknown action\"}");
318
+ } catch (Exception e) {
319
+ return Map.of("statusCode", 500, "body", "{\"error\":\"" + e.getMessage() + "\"}");
320
+ }
321
+ }
322
+ }
323
+ ```
324
+
325
+ ### Common Patterns
326
+
327
+ #### Parameterized Queries (Prevent SQL Injection)
328
+
329
+ ```javascript
330
+ // GOOD - parameterized
331
+ await db.query('SELECT * FROM users WHERE id = $1', [userId]);
332
+
333
+ // BAD - string concatenation (NEVER DO THIS)
334
+ await db.query(`SELECT * FROM users WHERE id = ${userId}`);
335
+ ```
336
+
337
+ #### Pagination
338
+
339
+ ```javascript
340
+ const page = data.page || 1;
341
+ const limit = data.limit || 20;
342
+ const offset = (page - 1) * limit;
343
+
344
+ const { rows } = await db.query(
345
+ 'SELECT * FROM items ORDER BY created_at DESC LIMIT $1 OFFSET $2',
346
+ [limit, offset]
347
+ );
348
+
349
+ const { rows: [{ count }] } = await db.query('SELECT COUNT(*) FROM items');
350
+
351
+ return { items: rows, total: parseInt(count), page, limit };
352
+ ```
353
+
354
+ #### Search
355
+
356
+ ```javascript
357
+ const { rows } = await db.query(
358
+ 'SELECT * FROM items WHERE name ILIKE $1',
359
+ [`%${data.query}%`]
360
+ );
361
+ ```
362
+
363
+ #### JSON Columns
364
+
365
+ ```javascript
366
+ // Create table with JSONB column
367
+ await db.query(`
368
+ CREATE TABLE IF NOT EXISTS products (
369
+ id SERIAL PRIMARY KEY,
370
+ name TEXT,
371
+ metadata JSONB DEFAULT '{}'
372
+ )
373
+ `);
374
+
375
+ // Query JSON
376
+ const { rows } = await db.query(
377
+ "SELECT * FROM products WHERE metadata->>'category' = $1",
378
+ ['electronics']
379
+ );
380
+
381
+ // Update JSON field
382
+ await db.query(
383
+ "UPDATE products SET metadata = jsonb_set(metadata, '{stock}', $1) WHERE id = $2",
384
+ [JSON.stringify(100), productId]
385
+ );
386
+ ```
387
+
388
+ #### Transactions
389
+
390
+ ```javascript
391
+ try {
392
+ await db.query('BEGIN');
393
+ await db.query('UPDATE accounts SET balance = balance - $1 WHERE id = $2', [amount, fromId]);
394
+ await db.query('UPDATE accounts SET balance = balance + $1 WHERE id = $2', [amount, toId]);
395
+ await db.query('COMMIT');
396
+ } catch (e) {
397
+ await db.query('ROLLBACK');
398
+ throw e;
399
+ }
400
+ ```
401
+
402
+ ### Postgres CLI Commands
403
+
404
+ ```bash
405
+ openkbs postgres enable # Enable database
406
+ openkbs postgres status # Check status
407
+ openkbs postgres connection # Get connection string
408
+ openkbs postgres disable # Disable (WARNING: deletes data)
409
+ ```
410
+
411
+ ---
412
+
413
+ ## S3 Storage & CloudFront CDN
414
+
415
+ Upload files, serve images, and host media with S3 storage and CloudFront CDN. Get presigned URLs for secure browser uploads.
416
+
417
+ ### Enable Storage
418
+
419
+ ```bash
420
+ openkbs storage enable
421
+ ```
422
+
423
+ Your S3 bucket is ready.
424
+
425
+ ### Check Status
426
+
427
+ ```bash
428
+ openkbs storage status
429
+ ```
430
+
431
+ Output:
432
+ ```
433
+ Storage Status:
434
+ Enabled: true
435
+ Bucket: openkbs-elastic-abc123
436
+ Region: us-east-1
437
+ Public: false
438
+ ```
439
+
440
+ ### Add CloudFront CDN
441
+
442
+ Serve files from your domain with edge caching:
443
+
444
+ ```bash
445
+ openkbs storage cloudfront media
446
+ ```
447
+
448
+ This maps:
449
+ - S3 path `media/*` to URL `yourdomain.com/media/*`
450
+
451
+ | S3 Key | Public URL |
452
+ |--------|------------|
453
+ | `media/photo.jpg` | `yourdomain.com/media/photo.jpg` |
454
+ | `media/uploads/image.png` | `yourdomain.com/media/uploads/image.png` |
455
+
456
+ ### Upload Files from CLI
457
+
458
+ ```bash
459
+ openkbs storage put ./photo.jpg media/photo.jpg
460
+ openkbs storage put ./document.pdf docs/document.pdf
461
+ ```
462
+
463
+ ### Generate Presigned Upload URL (Node.js)
464
+
465
+ ```javascript
466
+ import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';
467
+ import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
468
+
469
+ const s3 = new S3Client({ region: process.env.STORAGE_REGION });
470
+
471
+ export async function handler(event) {
472
+ const { action, fileName, contentType } = JSON.parse(event.body || '{}');
473
+
474
+ if (action === 'getUploadUrl') {
475
+ const bucket = process.env.STORAGE_BUCKET;
476
+
477
+ // Key must match CloudFront path prefix
478
+ const key = `media/uploads/${Date.now()}-${fileName}`;
479
+
480
+ const command = new PutObjectCommand({
481
+ Bucket: bucket,
482
+ Key: key,
483
+ ContentType: contentType || 'application/octet-stream'
484
+ });
485
+
486
+ const uploadUrl = await getSignedUrl(s3, command, { expiresIn: 3600 });
487
+
488
+ // Return relative URL for CloudFront
489
+ const publicUrl = `/${key}`;
490
+
491
+ return {
492
+ statusCode: 200,
493
+ body: JSON.stringify({ uploadUrl, publicUrl, key })
494
+ };
495
+ }
496
+ }
497
+ ```
498
+
499
+ ### Generate Presigned Upload URL (Python)
500
+
501
+ ```python
502
+ import json
503
+ import os
504
+ import boto3
505
+ from botocore.config import Config
506
+
507
+ s3 = boto3.client('s3',
508
+ region_name=os.environ.get('STORAGE_REGION', 'us-east-1'),
509
+ config=Config(signature_version='s3v4')
510
+ )
511
+
512
+ def handler(event, context):
513
+ body = json.loads(event.get('body', '{}'))
514
+ action = body.get('action')
515
+ bucket = os.environ['STORAGE_BUCKET']
516
+
517
+ if action == 'getUploadUrl':
518
+ import time
519
+ key = f"media/uploads/{int(time.time())}-{body['fileName']}"
520
+
521
+ url = s3.generate_presigned_url('put_object',
522
+ Params={'Bucket': bucket, 'Key': key, 'ContentType': body.get('contentType', 'application/octet-stream')},
523
+ ExpiresIn=3600
524
+ )
525
+
526
+ return {
527
+ 'statusCode': 200,
528
+ 'body': json.dumps({'uploadUrl': url, 'publicUrl': f'/{key}', 'key': key})
529
+ }
530
+
531
+ return {'statusCode': 400, 'body': json.dumps({'error': 'Unknown action'})}
532
+ ```
533
+
534
+ ### Browser Upload
535
+
536
+ ```javascript
537
+ async function uploadFile(file) {
538
+ // 1. Get presigned URL from your API
539
+ const response = await fetch('/api', {
540
+ method: 'POST',
541
+ headers: { 'Content-Type': 'application/json' },
542
+ body: JSON.stringify({
543
+ action: 'getUploadUrl',
544
+ fileName: file.name,
545
+ contentType: file.type
546
+ })
547
+ });
548
+
549
+ const { uploadUrl, publicUrl } = await response.json();
550
+
551
+ // 2. Upload directly to S3
552
+ await fetch(uploadUrl, {
553
+ method: 'PUT',
554
+ body: file,
555
+ headers: { 'Content-Type': file.type }
556
+ });
557
+
558
+ // 3. Return the public URL
559
+ return publicUrl; // e.g., /media/uploads/1234567890-photo.jpg
560
+ }
561
+
562
+ // Usage
563
+ const fileInput = document.querySelector('input[type="file"]');
564
+ fileInput.addEventListener('change', async (e) => {
565
+ const file = e.target.files[0];
566
+ const url = await uploadFile(file);
567
+ console.log('Uploaded to:', url);
568
+ });
569
+ ```
570
+
571
+ ### Upload with Progress
572
+
573
+ ```javascript
574
+ function uploadWithProgress(file, onProgress) {
575
+ return new Promise(async (resolve, reject) => {
576
+ const { uploadUrl, publicUrl } = await fetch('/api', {
577
+ method: 'POST',
578
+ headers: { 'Content-Type': 'application/json' },
579
+ body: JSON.stringify({
580
+ action: 'getUploadUrl',
581
+ fileName: file.name,
582
+ contentType: file.type
583
+ })
584
+ }).then(r => r.json());
585
+
586
+ const xhr = new XMLHttpRequest();
587
+
588
+ xhr.upload.addEventListener('progress', (e) => {
589
+ if (e.lengthComputable) {
590
+ onProgress(Math.round((e.loaded / e.total) * 100));
591
+ }
592
+ });
593
+
594
+ xhr.addEventListener('load', () => {
595
+ if (xhr.status === 200) resolve(publicUrl);
596
+ else reject(new Error('Upload failed'));
597
+ });
598
+
599
+ xhr.addEventListener('error', reject);
600
+ xhr.open('PUT', uploadUrl);
601
+ xhr.setRequestHeader('Content-Type', file.type);
602
+ xhr.send(file);
603
+ });
604
+ }
605
+
606
+ // Usage
607
+ const url = await uploadWithProgress(file, (percent) => {
608
+ console.log(`Upload: ${percent}%`);
609
+ });
610
+ ```
611
+
612
+ ### Storage CLI Commands
613
+
614
+ ```bash
615
+ openkbs storage enable # Enable storage
616
+ openkbs storage status # Check status
617
+ openkbs storage public true|false # Set public access
618
+ openkbs storage cloudfront <path> # Add CloudFront path
619
+ openkbs storage cloudfront remove <path> # Remove CloudFront path
620
+ openkbs storage ls [prefix] # List files
621
+ openkbs storage put <file> [key] # Upload file
622
+ openkbs storage get <key> [file] # Download file
623
+ openkbs storage rm <key> # Delete file
624
+ openkbs storage disable --force # Disable (DANGEROUS)
625
+ ```
626
+
627
+ ---
628
+
629
+ ## Serverless Functions
630
+
631
+ Deploy serverless APIs with AWS Lambda. Write your code in Node.js, Python, or Java. Get HTTPS endpoints automatically.
632
+
633
+ ### Project Structure
634
+
635
+ ```
636
+ my-app/
637
+ ├── openkbs.json
638
+ ├── functions/
639
+ │ └── api/
640
+ │ ├── index.mjs # Node.js handler
641
+ │ └── package.json # Dependencies
642
+ └── site/
643
+ └── index.html
644
+ ```
645
+
646
+ ### Node.js Function
647
+
648
+ Create `functions/api/index.mjs`:
649
+
650
+ ```javascript
651
+ export async function handler(event) {
652
+ const { action, ...data } = JSON.parse(event.body || '{}');
653
+
654
+ switch (action) {
655
+ case 'hello':
656
+ return {
657
+ statusCode: 200,
658
+ headers: { 'Content-Type': 'application/json' },
659
+ body: JSON.stringify({ message: `Hello, ${data.name || 'World'}!` })
660
+ };
661
+
662
+ case 'echo':
663
+ return {
664
+ statusCode: 200,
665
+ body: JSON.stringify({ received: data })
666
+ };
667
+
668
+ default:
669
+ return {
670
+ statusCode: 400,
671
+ body: JSON.stringify({ error: 'Unknown action' })
672
+ };
673
+ }
674
+ }
675
+ ```
676
+
677
+ Create `functions/api/package.json`:
678
+
679
+ ```json
680
+ {
681
+ "type": "module",
682
+ "dependencies": {}
683
+ }
684
+ ```
685
+
686
+ ### Python Function
687
+
688
+ Create `functions/api/handler.py`:
689
+
690
+ ```python
691
+ import json
692
+
693
+ def handler(event, context):
694
+ body = json.loads(event.get('body', '{}'))
695
+ action = body.get('action')
696
+
697
+ if action == 'hello':
698
+ name = body.get('name', 'World')
699
+ return {
700
+ 'statusCode': 200,
701
+ 'body': json.dumps({'message': f'Hello, {name}!'})
702
+ }
703
+
704
+ return {
705
+ 'statusCode': 400,
706
+ 'body': json.dumps({'error': 'Unknown action'})
707
+ }
708
+ ```
709
+
710
+ ### Java Function
711
+
712
+ Create `functions/api/src/main/java/com/example/Handler.java`:
713
+
714
+ ```java
715
+ package com.example;
716
+
717
+ import com.amazonaws.services.lambda.runtime.Context;
718
+ import com.amazonaws.services.lambda.runtime.RequestHandler;
719
+ import com.google.gson.Gson;
720
+ import java.util.Map;
721
+
722
+ public class Handler implements RequestHandler<Map<String, Object>, Map<String, Object>> {
723
+ private static final Gson gson = new Gson();
724
+
725
+ @Override
726
+ public Map<String, Object> handleRequest(Map<String, Object> event, Context context) {
727
+ String body = (String) event.get("body");
728
+ Map<String, Object> request = gson.fromJson(body, Map.class);
729
+ String action = (String) request.get("action");
730
+
731
+ if ("hello".equals(action)) {
732
+ String name = (String) request.getOrDefault("name", "World");
733
+ return Map.of(
734
+ "statusCode", 200,
735
+ "body", gson.toJson(Map.of("message", "Hello, " + name + "!"))
736
+ );
737
+ }
738
+
739
+ return Map.of("statusCode", 400, "body", "{\"error\":\"Unknown action\"}");
740
+ }
741
+ }
742
+ ```
743
+
744
+ ### Deploy Function
745
+
746
+ ```bash
747
+ # Install dependencies
748
+ cd functions/api && npm install && cd ../..
749
+
750
+ # Deploy function
751
+ openkbs fn push api
752
+ ```
753
+
754
+ Your function is live at `https://yourdomain.com/api`.
755
+
756
+ ### Test Function
757
+
758
+ ```bash
759
+ # From CLI
760
+ openkbs fn invoke api '{"action":"hello","name":"OpenKBS"}'
761
+
762
+ # With curl
763
+ curl -X POST https://yourdomain.com/api \
764
+ -H "Content-Type: application/json" \
765
+ -d '{"action":"hello","name":"OpenKBS"}'
766
+ ```
767
+
768
+ ### Configure Memory & Timeout
769
+
770
+ ```bash
771
+ # Default: 128MB memory, 30s timeout
772
+ openkbs fn push api
773
+
774
+ # More memory (= more CPU)
775
+ openkbs fn push api --memory 512
776
+
777
+ # Longer timeout (max 900s)
778
+ openkbs fn push api --timeout 60
779
+
780
+ # Both
781
+ openkbs fn push api --memory 1024 --timeout 120
782
+ ```
783
+
784
+ Memory options: 128, 256, 512, 1024, 2048, 3008 MB
785
+
786
+ ### Environment Variables
787
+
788
+ ```bash
789
+ # View environment
790
+ openkbs fn env api
791
+
792
+ # Set custom variables
793
+ openkbs fn env api STRIPE_KEY=sk_live_xxx
794
+ openkbs fn env api DEBUG=true
795
+
796
+ # Remove variable
797
+ openkbs fn env api STRIPE_KEY=
798
+ ```
799
+
800
+ ### CORS Headers
801
+
802
+ For browser access, add CORS headers:
803
+
804
+ ```javascript
805
+ export async function handler(event) {
806
+ const headers = {
807
+ 'Content-Type': 'application/json',
808
+ 'Access-Control-Allow-Origin': '*',
809
+ 'Access-Control-Allow-Headers': 'Content-Type'
810
+ };
811
+
812
+ // Handle OPTIONS preflight
813
+ if (event.requestContext?.http?.method === 'OPTIONS') {
814
+ return { statusCode: 200, headers, body: '' };
815
+ }
816
+
817
+ // Your logic here...
818
+
819
+ return { statusCode: 200, headers, body: JSON.stringify({ success: true }) };
820
+ }
821
+ ```
822
+
823
+ ### Multiple Functions
824
+
825
+ ```
826
+ functions/
827
+ ├── auth/
828
+ │ ├── index.mjs
829
+ │ └── package.json
830
+ ├── posts/
831
+ │ ├── index.mjs
832
+ │ └── package.json
833
+ └── payments/
834
+ ├── index.mjs
835
+ └── package.json
836
+ ```
837
+
838
+ `openkbs.json`:
839
+ ```json
840
+ {
841
+ "name": "my-app",
842
+ "elastic": { "postgres": true },
843
+ "functions": ["auth", "posts", "payments"]
844
+ }
845
+ ```
846
+
847
+ Deploy all at once:
848
+ ```bash
849
+ openkbs deploy
850
+ ```
851
+
852
+ Or individually:
853
+ ```bash
854
+ openkbs fn push auth
855
+ openkbs fn push posts
856
+ ```
857
+
858
+ ### Functions CLI Commands
859
+
860
+ ```bash
861
+ openkbs fn list # List functions
862
+ openkbs fn push <name> # Deploy function
863
+ openkbs fn push <name> --memory 512 # With memory
864
+ openkbs fn push <name> --timeout 60 # With timeout
865
+ openkbs fn logs <name> # View logs
866
+ openkbs fn logs <name> --follow # Stream logs
867
+ openkbs fn env <name> # View env vars
868
+ openkbs fn env <name> KEY=value # Set env var
869
+ openkbs fn invoke <name> '{}' # Test invoke
870
+ openkbs fn delete <name> # Delete function
871
+ ```
872
+
873
+ ---
874
+
875
+ ## Real-time Pulse
876
+
877
+ Add real-time features to your app with Pulse WebSocket messaging. Build live updates, chat, presence tracking, and collaborative features.
878
+
879
+ ### Enable Pulse
880
+
881
+ ```bash
882
+ openkbs pulse enable
883
+ ```
884
+
885
+ ### How It Works
886
+
887
+ ```
888
+ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐
889
+ │ Browser 1 │ │ Pulse │ │ Browser 2 │
890
+ │ │────▶│ Server │────▶│ │
891
+ │ Subscribe │ │ │ │ Receive │
892
+ │ to 'posts' │ │ │ │ message │
893
+ └──────────────┘ └──────────────┘ └──────────────┘
894
+
895
+ │ Publish
896
+ ┌──────────────┐
897
+ │ Lambda │
898
+ │ Function │
899
+ └──────────────┘
900
+ ```
901
+
902
+ 1. Clients connect via WebSocket and subscribe to channels
903
+ 2. Your Lambda publishes events to channels
904
+ 3. All subscribers receive the message instantly
905
+
906
+ ### Client SDK (Browser)
907
+
908
+ Install:
909
+
910
+ ```html
911
+ <script src="https://unpkg.com/openkbs-pulse@2.0.1/pulse.js"></script>
912
+ ```
913
+
914
+ Or with npm:
915
+ ```bash
916
+ npm install openkbs-pulse
917
+ ```
918
+
919
+ Connect:
920
+
921
+ ```javascript
922
+ // Get token from your API (see Server SDK below)
923
+ const { token, endpoint, kbId } = await fetch('/auth', {
924
+ method: 'POST',
925
+ body: JSON.stringify({ action: 'getPulseToken', userId: 'user123' })
926
+ }).then(r => r.json());
927
+
928
+ // Connect to Pulse
929
+ const realtime = new Pulse.Realtime({
930
+ kbId,
931
+ token,
932
+ endpoint,
933
+ clientId: 'user123'
934
+ });
935
+
936
+ // Connection events
937
+ realtime.connection.on('connected', () => console.log('Connected!'));
938
+ realtime.connection.on('disconnected', () => console.log('Disconnected'));
939
+ ```
940
+
941
+ Subscribe to channel:
942
+
943
+ ```javascript
944
+ const channel = realtime.channels.get('posts');
945
+
946
+ // Subscribe to specific event
947
+ channel.subscribe('new_post', (message) => {
948
+ console.log('New post:', message.data);
949
+ });
950
+
951
+ // Subscribe to all events
952
+ channel.subscribe((message) => {
953
+ console.log('Event:', message.name, message.data);
954
+ });
955
+ ```
956
+
957
+ Presence (who's online):
958
+
959
+ ```javascript
960
+ const channel = realtime.channels.get('posts');
961
+
962
+ // Enter presence with data
963
+ channel.presence.enter({ name: 'Alice', avatar: '/alice.jpg' });
964
+
965
+ // Get current members
966
+ channel.presence.get((members) => {
967
+ console.log('Online:', members.length);
968
+ members.forEach(m => console.log(m.data.name));
969
+ });
970
+
971
+ // Subscribe to presence changes
972
+ channel.presence.subscribe((members) => {
973
+ console.log('Members updated:', members);
974
+ });
975
+
976
+ // Specific events
977
+ channel.presence.subscribe('enter', (member) => {
978
+ console.log(`${member.data.name} joined`);
979
+ });
980
+
981
+ channel.presence.subscribe('leave', (member) => {
982
+ console.log(`${member.data.name} left`);
983
+ });
984
+
985
+ // Leave when done
986
+ channel.presence.leave();
987
+ ```
988
+
989
+ ### Server SDK (Lambda)
990
+
991
+ Generate token:
992
+
993
+ ```javascript
994
+ import pulse from 'openkbs-pulse/server';
995
+
996
+ export async function handler(event) {
997
+ const { action, userId } = JSON.parse(event.body || '{}');
998
+ const kbId = process.env.OPENKBS_KB_ID;
999
+ const apiKey = process.env.OPENKBS_API_KEY;
1000
+
1001
+ if (action === 'getPulseToken') {
1002
+ const tokenData = await pulse.getToken(kbId, apiKey, userId);
1003
+ return {
1004
+ statusCode: 200,
1005
+ body: JSON.stringify({
1006
+ token: tokenData.token,
1007
+ endpoint: tokenData.endpoint,
1008
+ kbId
1009
+ })
1010
+ };
1011
+ }
1012
+ }
1013
+ ```
1014
+
1015
+ Publish events:
1016
+
1017
+ ```javascript
1018
+ import pulse from 'openkbs-pulse/server';
1019
+
1020
+ export async function handler(event) {
1021
+ const { action, ...data } = JSON.parse(event.body || '{}');
1022
+ const kbId = process.env.OPENKBS_KB_ID;
1023
+ const apiKey = process.env.OPENKBS_API_KEY;
1024
+
1025
+ if (action === 'createPost') {
1026
+ // Save to database...
1027
+ const post = { id: 1, title: data.title, content: data.content };
1028
+
1029
+ // Publish to all subscribers
1030
+ await pulse.publish('posts', 'new_post', { post }, { kbId, apiKey });
1031
+
1032
+ return { statusCode: 200, body: JSON.stringify({ post }) };
1033
+ }
1034
+ }
1035
+ ```
1036
+
1037
+ Get presence:
1038
+
1039
+ ```javascript
1040
+ const presence = await pulse.presence('posts', { kbId, apiKey });
1041
+ console.log('Online count:', presence.count);
1042
+ console.log('Members:', presence.members);
1043
+ ```
1044
+
1045
+ ### Private Channels
1046
+
1047
+ For private messaging, use secret channel IDs:
1048
+
1049
+ ```javascript
1050
+ // Generate unique channel ID per user (store in database)
1051
+ const privateChannel = crypto.randomBytes(32).toString('hex');
1052
+
1053
+ // User subscribes to their own private channel
1054
+ const myChannel = realtime.channels.get(user.privateChannel);
1055
+ myChannel.subscribe('message', (msg) => {
1056
+ console.log('Private message:', msg.data);
1057
+ });
1058
+
1059
+ // Server publishes to recipient's channel
1060
+ await pulse.publish(recipient.privateChannel, 'message', {
1061
+ from: sender.name,
1062
+ text: 'Hello!'
1063
+ }, { kbId, apiKey });
1064
+ ```
1065
+
1066
+ ### Pulse CLI Commands
1067
+
1068
+ ```bash
1069
+ openkbs pulse enable # Enable Pulse
1070
+ openkbs pulse status # Check status
1071
+ openkbs pulse channels # List active channels
1072
+ openkbs pulse presence <channel> # View presence
1073
+ openkbs pulse publish <ch> "msg" # Publish message
1074
+ openkbs pulse disable # Disable
1075
+ ```
1076
+
1077
+ ---
1078
+
1079
+ ## CLI Reference
1080
+
1081
+ ### General Commands
1082
+
1083
+ ```bash
1084
+ openkbs init # Initialize project
1085
+ openkbs deploy # Deploy everything
1086
+ openkbs ls # List your KBs
1087
+ openkbs status # Show project status
1088
+ ```
1089
+
1090
+ ### Postgres Commands
1091
+
1092
+ ```bash
1093
+ openkbs postgres enable # Enable database
1094
+ openkbs postgres status # Check status
1095
+ openkbs postgres connection # Get connection string
1096
+ openkbs postgres disable # Disable (deletes data!)
1097
+ ```
1098
+
1099
+ ### Storage Commands
1100
+
1101
+ ```bash
1102
+ openkbs storage enable # Enable storage
1103
+ openkbs storage status # Check status
1104
+ openkbs storage public true|false # Set public access
1105
+ openkbs storage cloudfront <path> # Add CloudFront path
1106
+ openkbs storage cloudfront remove <path> # Remove CloudFront path
1107
+ openkbs storage ls [prefix] # List files
1108
+ openkbs storage put <file> [key] # Upload file
1109
+ openkbs storage get <key> [file] # Download file
1110
+ openkbs storage rm <key> # Delete file
1111
+ openkbs storage disable --force # Disable (dangerous!)
1112
+ ```
1113
+
1114
+ ### Function Commands
1115
+
1116
+ ```bash
1117
+ openkbs fn list # List functions
1118
+ openkbs fn push <name> # Deploy function
1119
+ openkbs fn push <name> --memory 512 # With memory setting
1120
+ openkbs fn push <name> --timeout 60 # With timeout setting
1121
+ openkbs fn logs <name> # View logs
1122
+ openkbs fn logs <name> --follow # Stream logs live
1123
+ openkbs fn logs <name> --limit 100 # Limit log entries
1124
+ openkbs fn env <name> # View environment variables
1125
+ openkbs fn env <name> KEY=value # Set environment variable
1126
+ openkbs fn env <name> KEY= # Remove environment variable
1127
+ openkbs fn invoke <name> '{json}' # Test invoke function
1128
+ openkbs fn delete <name> # Delete function
1129
+ ```
1130
+
1131
+ ### Pulse Commands
1132
+
1133
+ ```bash
1134
+ openkbs pulse enable # Enable Pulse
1135
+ openkbs pulse status # Check status
1136
+ openkbs pulse channels # List active channels
1137
+ openkbs pulse presence <channel> # View channel presence
1138
+ openkbs pulse publish <ch> "msg" # Publish message to channel
1139
+ openkbs pulse disable # Disable Pulse
1140
+ ```
1141
+
1142
+ ---
1143
+
1144
+ ## Example Apps
1145
+
1146
+ For complete working examples, see the tutorials:
1147
+
1148
+ - **[Node.js Full-Stack App](/tutorials/nodejs-fullstack/)** - Social app with posts, real-time chat, image uploads, and presence
1149
+ - **[Java REST API](/tutorials/java-api/)** - CRUD API with PostgreSQL
1150
+ - **[Python API](/tutorials/python-api/)** - REST API with S3 file uploads