# TinyHost Backend-as-a-Service API Documentation TinyHost is a generic backend service built on Cloudflare Workers and Durable Objects, providing user authentication, real-time data synchronization, and TinyBase store operations. ## Overview TinyHost provides: - User authentication with session management - WebSocket-based real-time synchronization using TinyBase - Per-user isolated data storage via Durable Objects - RESTful API endpoints for data operations - Real-time multi-client synchronization - Automatic server heartbeat for connection monitoring ## Authentication ### Sign Up Create a new user account. ``` POST /your-namespace/sign-up Content-Type: application/json { "email": "user@example.com", "password": "secure_password" } Response: { "success": true, "message": "Account created successfully" } ``` ### Sign In Authenticate and receive a session token. ``` POST /your-namespace/sign-in Content-Type: application/json { "email": "user@example.com", "password": "secure_password" } Response: { "session_uuid": "uuid-string", "user": { "email": "user@example.com", "created_at": "2025-01-01T00:00:00Z", "login_time": "2025-01-01T12:00:00Z" } } ``` ### Sign Out Invalidate the current session. ``` POST /your-namespace/sign-out Authorization: Bearer {session_uuid} Response: { "success": true, "message": "Signed out successfully" } ``` ### Get Session Info Retrieve current session and user information. ``` GET /api/session Authorization: Bearer {session_uuid} Response: { "session": { "uuid": "session-uuid", "created_at": "2025-01-01T00:00:00Z", "expires_at": "2026-01-01T00:00:00Z", "data": null }, "user": { "email": "user@example.com", "created_at": "2025-01-01T00:00:00Z", "durable_object_name": "unique-do-identifier" } } ``` ## WebSocket Real-Time Sync Connect to the WebSocket endpoint for real-time TinyBase synchronization: ``` wss://tinyhost.app/your-namespace/ws?sessionUuid={session_uuid} ``` Example JavaScript client: ```javascript const sessionUuid = 'your-session-uuid-from-sign-in'; const ws = new WebSocket(`wss://tinyhost.app/your-namespace/ws?sessionUuid=${sessionUuid}`); ws.onopen = () => { console.log('Connected to TinyHost'); // TinyBase synchronization begins automatically }; ``` Each user gets their own isolated Durable Object (with unique UUID identifier) for data storage and synchronization. The durable object name is automatically generated and managed internally. ## Server Heartbeat TinyHost automatically sends a heartbeat every 10 seconds to monitor server connectivity and health. This appears as a special `heartbeat` value in your TinyBase store. ### Heartbeat Value - **Value ID**: `heartbeat` - **Update Frequency**: Every 10 seconds (starts after first interval) - **Format**: ISO timestamp string (e.g., `"2025-09-29T03:24:22.539Z"`) - **Purpose**: Connection health monitoring and server status - **Availability**: Appears after the store is initialized and in use ### Monitoring Heartbeat in Your Application ```javascript // Monitor heartbeat using a listener (recommended approach) store.addValueListener('heartbeat', (store, valueId, newValue) => { console.log('Server heartbeat:', newValue); // Check if heartbeat is recent (within last 15 seconds) const heartbeatTime = new Date(newValue); const now = new Date(); const timeDiff = now - heartbeatTime; if (timeDiff > 15000) { console.warn('Server heartbeat is stale'); } else { console.log('Server connection healthy'); } }); // Optional: Check current value only if needed for initialization const currentHeartbeat = store.getValue('heartbeat'); if (currentHeartbeat) { console.log('Initial server heartbeat:', currentHeartbeat); } ``` The heartbeat is automatically managed by the server and requires no client-side configuration. It helps applications detect connection issues and server health status. Note that the heartbeat will only appear after your application has started using the store (which initializes the server-side persister). ## Key Features Tested ✅ **User Authentication** - Sign up, sign in, sign out ✅ **WebSocket Synchronization** - Real-time data sync using TinyBase ✅ **User Isolation** - Each user has their own isolated data store ✅ **Multi-Client Sync** - Multiple browser tabs/devices sync the same user's data ✅ **Automatic Persistence** - All data is automatically saved and persisted ✅ **Server Heartbeat** - Automatic connection health monitoring every 10 seconds ## TinyBase Client Integration Use TinyBase's WebSocket synchronization to connect to your user's data store: ```javascript import { createMergeableStore } from 'tinybase'; import { createWsSynchronizer } from 'tinybase/synchronizers/synchronizer-ws-client'; // After user signs in, you get a session_uuid const sessionUuid = 'uuid-from-sign-in'; // Create a TinyBase store const store = createMergeableStore(); // Connect to WebSocket for real-time sync const ws = new WebSocket(`wss://tinyhost.app/your-namespace/ws?sessionUuid=${sessionUuid}`); const synchronizer = await createWsSynchronizer(store, ws); // Start synchronization await synchronizer.startSync(); // Now you can use TinyBase normally - all changes sync automatically store.setCell('tasks', 'task1', 'title', 'Learn TinyBase'); store.setCell('tasks', 'task1', 'completed', false); // Listen for changes from other clients store.addTablesListener(() => { console.log('Data changed!', store.getTables()); }); // Monitor server heartbeat for connection health store.addValueListener('heartbeat', (store, valueId, newValue) => { if (valueId === 'heartbeat') { console.log('Server is alive:', newValue); } }); ``` ## Complete Example Here's a complete example of using TinyHost in a web application: ```javascript // 1. User Authentication async function authenticateUser() { // Sign up new user const signUpResponse = await fetch('https://tinyhost.app/your-namespace/sign-up', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email: 'user@example.com', password: 'secure_password' }) }); // Sign in to get session const signInResponse = await fetch('https://tinyhost.app/your-namespace/sign-in', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email: 'user@example.com', password: 'secure_password' }) }); const { session_uuid } = await signInResponse.json(); return session_uuid; } // 2. Create TinyBase Store with WebSocket Sync async function createSyncedStore(sessionUuid) { const store = createMergeableStore(); const ws = new WebSocket(`wss://tinyhost.app/your-namespace/ws?sessionUuid=${sessionUuid}`); const synchronizer = await createWsSynchronizer(store, ws); await synchronizer.startSync(); return store; } // 3. Use the store const sessionUuid = await authenticateUser(); const store = await createSyncedStore(sessionUuid); // Add data - automatically syncs to server and other clients store.setCell('tasks', 'task1', 'title', 'Learn TinyBase'); store.setCell('tasks', 'task1', 'completed', false); // Listen for changes from other clients store.addTablesListener(() => { console.log('Data updated:', store.getTables()); }); // Monitor server connection health store.addValueListener('heartbeat', (store, valueId, newValue) => { console.log('Server heartbeat received:', newValue); }); ``` ## Health Check ```bash GET https://tinyhost.app/health ``` Returns service status and timestamp. ## Manual Sync Protocol When WebSocket synchronization fails due to message size limits (error code 1009 - message too large), use this HTTP-based manual sync protocol to sync large data. ### WebSocket Message Size Limits WebSocket connections have a message size limit (approximately 1MB). When your data exceeds this limit: 1. WebSocket will close with code 1009 2. Use the Manual Sync endpoint to sync via HTTP 3. After manual sync, reconnect WebSocket for normal operation ### POST /{namespace}/manual-sync Handles large data synchronization that exceeds WebSocket message limits. **How it works:** 1. Client detects 1009 WebSocket error (message too large) 2. **Client stops synchronizer and disconnects WebSocket** to prevent conflicts during manual sync 3. Client blocks UI/writes to prevent data changes during sync 4. Client gets its mergeable content using `store.getMergeableContent()` 5. Client sends mergeable content via HTTP POST to this endpoint 6. Server merges client data with existing server data using CRDTs 7. Server returns the merged content 8. Client replaces its store content with `store.setMergeableContent(mergedContent)` 9. Client reconnects WebSocket and restarts synchronizer for normal operation **Request:** ```http POST https://tinyhost.app/your-namespace/manual-sync Content-Type: application/json X-Session-UUID: {session_uuid_from_sign_in} { "mergeableContent": { // Output from store.getMergeableContent() // This is the client's complete mergeable store content // Includes all tables, values, and CRDT metadata } } ``` **Response:** ```json { "success": true, "mergeableContent": { // Merged content from server // Client should call store.setMergeableContent() with this }, "timestamp": "2024-01-01T00:00:00.000Z" } ``` **Error Response:** ```json { "error": "error_message", "message": "detailed_error_description" } ``` ### Client Implementation Example ```javascript import { createMergeableStore } from 'tinybase'; import { createWsSynchronizer } from 'tinybase/synchronizers/synchronizer-ws-client'; class TinyHostClient { constructor(namespace, sessionUuid) { this.namespace = namespace; this.sessionUuid = sessionUuid; this.store = createMergeableStore(); this.synchronizer = null; this.ws = null; } async connect() { try { // Try WebSocket connection await this.connectWebSocket(); } catch (error) { if (error.code === 1009) { // Handle message too large error await this.performManualSync(); } else { throw error; } } } async connectWebSocket() { const wsUrl = `wss://tinyhost.app/${this.namespace}/ws?sessionUuid=${this.sessionUuid}`; this.ws = new WebSocket(wsUrl); return new Promise((resolve, reject) => { this.ws.onopen = async () => { this.synchronizer = await createWsSynchronizer(this.store, this.ws); await this.synchronizer.startSync(); resolve(); }; this.ws.onclose = (event) => { if (event.code === 1009) { reject({ code: 1009, reason: 'Message too large' }); } }; this.ws.onerror = (error) => reject(error); }); } async performManualSync() { console.log('Performing manual sync for large data...'); // 1. Stop synchronizer and disconnect WebSocket to prevent conflicts if (this.synchronizer) { await this.synchronizer.destroy(); this.synchronizer = null; } if (this.ws && this.ws.readyState === WebSocket.OPEN) { this.ws.close(); } // 2. Block UI/writes during manual sync this.blockUI(true); try { // 3. Get client's mergeable content const clientContent = this.store.getMergeableContent(); console.log(`Sending ${JSON.stringify(clientContent).length / 1024}KB for manual sync`); // 4. Send to server for merging const response = await fetch(`https://tinyhost.app/${this.namespace}/manual-sync`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Session-UUID': this.sessionUuid }, body: JSON.stringify({ mergeableContent: clientContent }) }); if (!response.ok) { throw new Error(`Manual sync failed: ${response.status}`); } const result = await response.json(); // 5. Replace client store with merged content this.store.setMergeableContent(result.mergeableContent); console.log('Store updated with merged content from server'); // 6. Re-establish WebSocket and synchronizer for normal operation await this.connectWebSocket(); console.log('WebSocket and synchronizer reconnected for incremental updates'); } finally { // 8. Unblock UI this.blockUI(false); } } blockUI(blocked) { // Implement UI blocking logic // Prevent writes to store during sync recovery if (blocked) { console.log('UI blocked during sync recovery'); } else { console.log('UI unblocked'); } } } // Usage async function main() { const client = new TinyHostClient('your-namespace', 'session-uuid'); await client.connect(); // Now safe to use the store client.store.setCell('tasks', 'task1', 'title', 'Large task'); } ``` ### Best Practices 1. **Use Only When Necessary**: Manual sync should ONLY be used when WebSocket fails with 1009 error - never preemptively 2. **Synchronizer Management**: Always stop the synchronizer and close WebSocket before manual sync to prevent conflicts 3. **UI Blocking**: Always block UI during manual sync to prevent data changes 4. **Automatic Fallback**: Implement automatic fallback to manual sync when 1009 errors occur ### Size Monitoring (For User Awareness Only) ```javascript // Monitor store size for user warnings only function getStoreSizeMB(store) { const content = store.getMergeableContent(); return JSON.stringify(content).length / (1024 * 1024); } // Warn users when approaching limits - DO NOT preemptively use manual sync function warnIfApproachingLimit(store) { const sizeMB = getStoreSizeMB(store); if (sizeMB > 0.8) { console.warn(`Store size ${sizeMB.toFixed(2)}MB approaching 1MB WebSocket limit`); // Show user warning - let them know sync may require manual fallback return true; } return false; } ```