UNPKG

@mindmakr/gs-websdk

Version:

Web SDK for Guru SaaS System - Complete JavaScript/TypeScript SDK for building applications with dynamic schema management

884 lines (759 loc) 24.8 kB
# 💻 Code Examples Real-world examples and integration patterns for the Guru SaaS Web SDK. ## 📋 Table of Contents - [Basic Usage](#basic-usage) - [React Integration](#react-integration) - [Vue.js Integration](#vuejs-integration) - [Advanced Patterns](#advanced-patterns) - [Error Handling](#error-handling) - [Multi-Tenant Applications](#multi-tenant-applications) ## 🚀 Basic Usage ### Simple Authentication ```typescript import { GuruSaaS } from '@mindmakr/gs-websdk'; // Production configuration (with reverse proxy) const client = new GuruSaaS({ baseUrl: 'https://your-api-domain.com' }); // OR Development configuration (direct service access) const devClient = new GuruSaaS({ authUrl: 'http://localhost:4000', globalDataUrl: 'http://localhost:5010', aiServiceUrl: 'http://localhost:4001', notificationUrl: 'http://localhost:5020' }); async function login() { try { const { user, tokens } = await client.login('user@example.com', 'password'); console.log(`Welcome ${user.name}!`); return user; } catch (error) { console.error('Login failed:', error.message); throw error; } } ``` ### Schema Management ```typescript async function createCustomerForm() { // Create a schema template const template = await client.schema.createSchemaTemplate({ name: 'Customer Registration', code: 'customer_registration', category: 'customer_management', schema_definition: { type: 'object', properties: { firstName: { type: 'string', title: 'First Name', minLength: 2 }, lastName: { type: 'string', title: 'Last Name', minLength: 2 }, email: { type: 'string', format: 'email', title: 'Email Address' }, phone: { type: 'string', pattern: '^[+]?[1-9]\\d{1,14}$', title: 'Phone Number' } }, required: ['firstName', 'lastName', 'email'] } }); return template; } async function submitCustomerData(templateId: string, customerData: any) { const instance = await client.schema.createSchemaInstance({ template_id: templateId, data: customerData }); return instance; } ``` ### Advanced Search System The SDK includes a comprehensive advanced search system with the following features: - **Dynamic Field Discovery**: Automatically searches all string fields in schema definitions - **Permission-Aware**: Only searches templates user has access to - **Full-Text Search**: PostgreSQL full-text search with ranking and highlighting - **Caching**: Redis-based result caching for improved performance (sub-100ms) - **Analytics**: Search performance tracking and optimization - **Nested Field Support**: Searches nested objects (e.g., "general.displayName") ```typescript async function searchCustomers(searchTerm: string, userId: string) { // Basic search across all accessible templates const basicResults = await client.schema.getSchemaInstances({ search: searchTerm, userId: userId, // REQUIRED: User ID for permission validation page: 1, limit: 20 }); console.log('Search results:', basicResults.data); console.log('Search performance:', basicResults.search_summary?.search_performance_ms + 'ms'); console.log('Templates searched:', basicResults.search_summary?.total_templates_searched); return basicResults; } async function advancedSearch(userId: string) { // Advanced search with filters and metadata const results = await client.schema.getSchemaInstances({ search: 'premium customer', entity_type: 'customer', template_ids: [1, 2, 3], // Search specific templates only include_metadata: true, relevance_threshold: 0.7, // Only high-relevance results (0-1 scale) userId: userId, // REQUIRED: User ID for permission validation page: 1, limit: 50 }); // Access search metadata for each result results.data.forEach(item => { if (item.search_metadata) { console.log(`Item: ${item.instance_data?.name || 'Unknown'}`); console.log(` Matched fields: ${item.search_metadata.matched_fields.join(', ')}`); console.log(` Relevance score: ${item.search_metadata.relevance_score.toFixed(3)}`); console.log(` Template ID: ${item.search_metadata.template_id}`); console.log(` Permission validated: ${item.search_metadata.permission_validated}`); } }); // Performance insights and search summary if (results.search_summary) { console.log('\nSearch Summary:'); console.log(` Templates searched: ${results.search_summary.total_templates_searched}`); console.log(` Fields searched: ${results.search_summary.fields_searched.join(', ')}`); console.log(` Search performance: ${results.search_summary.search_performance_ms}ms`); console.log(` Permission check: ${results.search_summary.permission_check_ms}ms`); console.log(` Accessible templates: ${results.search_summary.accessible_templates}`); console.log(` Total searchable fields: ${results.search_summary.total_searchable_fields}`); } return results; } async function searchWithPagination(searchTerm: string, userId: string) { let allResults = []; let page = 1; let hasMore = true; while (hasMore) { const results = await client.schema.getSchemaInstances({ search: searchTerm, userId: userId, page: page, limit: 100 }); allResults.push(...results.data); hasMore = page < results.pagination.totalPages; page++; } return allResults; } ``` ## ⚛️ React Integration ### Authentication Hook ```typescript // hooks/useAuth.ts import { useState, useEffect } from 'react'; import { GuruSaaS, User } from '@mindmakr/gs-websdk'; // Production configuration const client = new GuruSaaS({ baseUrl: process.env.REACT_APP_API_URL! }); // OR Development configuration const devClient = new GuruSaaS({ authUrl: process.env.REACT_APP_AUTH_URL || 'http://localhost:4000', globalDataUrl: process.env.REACT_APP_GLOBAL_DATA_URL || 'http://localhost:5010', aiServiceUrl: process.env.REACT_APP_AI_SERVICE_URL || 'http://localhost:4001', notificationUrl: process.env.REACT_APP_NOTIFICATION_URL || 'http://localhost:5020' }); export function useAuth() { const [user, setUser] = useState<User | null>(null); const [loading, setLoading] = useState(true); useEffect(() => { // Check if user is already authenticated if (client.isAuthenticated()) { client.getUserInfo() .then(setUser) .catch(() => setUser(null)) .finally(() => setLoading(false)); } else { setLoading(false); } }, []); const login = async (email: string, password: string) => { try { const { user } = await client.login(email, password); setUser(user); return user; } catch (error) { throw error; } }; const logout = async () => { await client.logout(); setUser(null); }; return { user, loading, login, logout, isAuthenticated: !!user }; } ``` ### Advanced Search Component ```typescript // components/AdvancedSearch.tsx import React, { useState, useEffect, useMemo } from 'react'; import { GuruSaaS, SchemaInstance } from '@mindmakr/gs-websdk'; import { useAuth } from '../hooks/useAuth'; interface SearchResult extends SchemaInstance { template_name?: string; template_code?: string; search_metadata?: { matched_fields: string[]; relevance_score: number; template_id: number; permission_validated: boolean; }; } export function AdvancedSearch() { const { user } = useAuth(); const [searchTerm, setSearchTerm] = useState(''); const [results, setResults] = useState<SearchResult[]>([]); const [loading, setLoading] = useState(false); const [searchSummary, setSearchSummary] = useState<any>(null); const [filters, setFilters] = useState({ entity_type: '', template_ids: [] as number[], include_metadata: true, relevance_threshold: 0.5 }); const client = new GuruSaaS({ baseUrl: process.env.REACT_APP_API_URL! }); // Debounced search const debouncedSearchTerm = useMemo(() => { const timer = setTimeout(() => searchTerm, 300); return () => clearTimeout(timer); }, [searchTerm]); useEffect(() => { if (searchTerm && user) { performSearch(); } else { setResults([]); setSearchSummary(null); } }, [debouncedSearchTerm, filters, user]); const performSearch = async () => { if (!user) return; setLoading(true); try { const response = await client.schema.getSchemaInstances({ search: searchTerm, userId: user.id, entity_type: filters.entity_type || undefined, template_ids: filters.template_ids.length > 0 ? filters.template_ids : undefined, include_metadata: filters.include_metadata, relevance_threshold: filters.relevance_threshold, page: 1, limit: 50 }); setResults(response.data); setSearchSummary(response.search_summary); } catch (error) { console.error('Search failed:', error); setResults([]); setSearchSummary(null); } finally { setLoading(false); } }; const highlightMatches = (text: string, matchedFields: string[]) => { // Simple highlighting logic let highlighted = text; matchedFields.forEach(field => { const regex = new RegExp(`(${searchTerm})`, 'gi'); highlighted = highlighted.replace(regex, '<mark>$1</mark>'); }); return highlighted; }; return ( <div className="advanced-search"> <div className="search-header"> <h2>Advanced Search</h2> <div className="search-input"> <input type="text" placeholder="Search across all fields..." value={searchTerm} onChange={(e) => setSearchTerm(e.target.value)} className="search-field" /> </div> <div className="search-filters"> <select value={filters.entity_type} onChange={(e) => setFilters({...filters, entity_type: e.target.value})} > <option value="">All Entity Types</option> <option value="customer">Customer</option> <option value="user">User</option> <option value="product">Product</option> </select> <label> <input type="checkbox" checked={filters.include_metadata} onChange={(e) => setFilters({...filters, include_metadata: e.target.checked})} /> Include Metadata </label> <label> Relevance Threshold: <input type="range" min="0" max="1" step="0.1" value={filters.relevance_threshold} onChange={(e) => setFilters({...filters, relevance_threshold: parseFloat(e.target.value)})} /> {filters.relevance_threshold} </label> </div> </div> {searchSummary && ( <div className="search-summary"> <p> Found {results.length} results in {searchSummary.search_performance_ms}ms across {searchSummary.total_templates_searched} templates </p> <p> Fields searched: {searchSummary.fields_searched.join(', ')} </p> </div> )} <div className="search-results"> {loading && <div className="loading">Searching...</div>} {results.map((result) => ( <div key={result.id} className="result-item"> <div className="result-header"> <h3>{result.template_name}</h3> <span className="entity-type">{result.entity_type}</span> {result.search_metadata && ( <span className="relevance-score"> Score: {result.search_metadata.relevance_score.toFixed(2)} </span> )} </div> <div className="result-content"> {Object.entries(result.instance_data).map(([key, value]) => ( <div key={key} className="field"> <strong>{key}:</strong> <span dangerouslySetInnerHTML={{ __html: result.search_metadata?.matched_fields.includes(key) ? highlightMatches(String(value), [key]) : String(value) }} /> </div> ))} </div> {result.search_metadata && ( <div className="search-metadata"> <small> Matched fields: {result.search_metadata.matched_fields.join(', ')} </small> </div> )} </div> ))} {!loading && results.length === 0 && searchTerm && ( <div className="no-results"> No results found for "{searchTerm}" </div> )} </div> </div> ); } ``` ### Schema Form Component ```typescript // components/DynamicForm.tsx import React, { useState, useEffect } from 'react'; import { GuruSaaS, SchemaTemplate } from '@mindmakr/gs-websdk'; interface DynamicFormProps { templateId: string; onSubmit: (data: any) => void; } export function DynamicForm({ templateId, onSubmit }: DynamicFormProps) { const [template, setTemplate] = useState<SchemaTemplate | null>(null); const [formData, setFormData] = useState<any>({}); const [loading, setLoading] = useState(true); const client = new GuruSaaS({ baseUrl: process.env.REACT_APP_API_URL! }); useEffect(() => { async function loadTemplate() { try { const template = await client.schema.getSchemaTemplate(templateId); setTemplate(template); } catch (error) { console.error('Failed to load template:', error); } finally { setLoading(false); } } loadTemplate(); }, [templateId]); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); try { const instance = await client.schema.createSchemaInstance({ template_id: templateId, data: formData }); onSubmit(instance); } catch (error) { console.error('Failed to submit form:', error); } }; if (loading) return <div>Loading...</div>; if (!template) return <div>Template not found</div>; return ( <form onSubmit={handleSubmit}> <h2>{template.name}</h2> {Object.entries(template.schema_definition.properties).map(([key, field]: [string, any]) => ( <div key={key} className="form-field"> <label htmlFor={key}>{field.title || key}</label> <input id={key} type={field.format === 'email' ? 'email' : 'text'} required={template.schema_definition.required?.includes(key)} value={formData[key] || ''} onChange={(e) => setFormData({ ...formData, [key]: e.target.value })} /> </div> ))} <button type="submit">Submit</button> </form> ); } ``` ### User Management Component ```typescript // components/UserManager.tsx import React, { useState, useEffect } from 'react'; import { GuruSaaS, User, PaginatedResponse } from '@mindmakr/gs-websdk'; export function UserManager() { const [users, setUsers] = useState<User[]>([]); const [loading, setLoading] = useState(true); const [page, setPage] = useState(1); const client = new GuruSaaS({ baseUrl: process.env.REACT_APP_API_URL! }); useEffect(() => { loadUsers(); }, [page]); const loadUsers = async () => { try { const response: PaginatedResponse<User> = await client.auth.getUsers({ page, limit: 10 }); setUsers(response.data); } catch (error) { console.error('Failed to load users:', error); } finally { setLoading(false); } }; const createUser = async (userData: Partial<User>) => { try { const newUser = await client.auth.createUser(userData); setUsers([...users, newUser]); } catch (error) { console.error('Failed to create user:', error); } }; if (loading) return <div>Loading users...</div>; return ( <div> <h2>User Management</h2> <div className="user-list"> {users.map(user => ( <div key={user.id} className="user-card"> <h3>{user.name}</h3> <p>{user.email}</p> <span className={`status ${user.is_active ? 'active' : 'inactive'}`}> {user.is_active ? 'Active' : 'Inactive'} </span> </div> ))} </div> <div className="pagination"> <button onClick={() => setPage(page - 1)} disabled={page === 1} > Previous </button> <span>Page {page}</span> <button onClick={() => setPage(page + 1)}> Next </button> </div> </div> ); } ``` ## 🖖 Vue.js Integration ### Authentication Composable ```typescript // composables/useAuth.ts import { ref, computed } from 'vue'; import { GuruSaaS, User } from '@mindmakr/gs-websdk'; const client = new GuruSaaS({ baseUrl: import.meta.env.VITE_API_URL }); const user = ref<User | null>(null); const loading = ref(false); export function useAuth() { const isAuthenticated = computed(() => !!user.value); const login = async (email: string, password: string) => { loading.value = true; try { const result = await client.login(email, password); user.value = result.user; return result; } finally { loading.value = false; } }; const logout = async () => { await client.logout(); user.value = null; }; const checkAuth = async () => { if (client.isAuthenticated()) { try { user.value = await client.getUserInfo(); } catch { user.value = null; } } }; return { user: readonly(user), loading: readonly(loading), isAuthenticated, login, logout, checkAuth }; } ``` ### Schema Template Manager ```vue <!-- components/SchemaManager.vue --> <template> <div class="schema-manager"> <h2>Schema Templates</h2> <div v-if="loading" class="loading"> Loading templates... </div> <div v-else class="template-grid"> <div v-for="template in templates" :key="template.id" class="template-card" @click="selectTemplate(template)" > <h3>{{ template.name }}</h3> <p>{{ template.description }}</p> <span class="category">{{ template.category }}</span> </div> </div> <button @click="createNewTemplate" class="create-btn"> Create New Template </button> </div> </template> <script setup lang="ts"> import { ref, onMounted } from 'vue'; import { GuruSaaS, SchemaTemplate } from '@mindmakr/gs-websdk'; const client = new GuruSaaS({ baseUrl: import.meta.env.VITE_API_URL }); const templates = ref<SchemaTemplate[]>([]); const loading = ref(true); const emit = defineEmits<{ templateSelected: [template: SchemaTemplate] }>(); onMounted(async () => { try { const response = await client.schema.getSchemaTemplates(); templates.value = response.data; } catch (error) { console.error('Failed to load templates:', error); } finally { loading.value = false; } }); const selectTemplate = (template: SchemaTemplate) => { emit('templateSelected', template); }; const createNewTemplate = () => { // Navigate to template creation page // or emit event to parent component }; </script> ``` ## 🔧 Advanced Patterns ### Custom Request Configuration ```typescript // Per-request configuration const templates = await client.schema.getSchemaTemplates({}, { timeout: 60000, tenantId: 'specific-tenant', headers: { 'X-Custom-Header': 'value' } }); ``` ### Batch Operations ```typescript async function batchEnhanceFields() { const enhancements = await client.ai.batchEnhanceFields([ { fieldName: 'email', fieldType: 'email', businessContext: 'customer registration', enhancementType: 'validation' }, { fieldName: 'phone', fieldType: 'phone', businessContext: 'customer contact', enhancementType: 'full' } ]); return enhancements; } ``` ### Event Handling ```typescript // Listen for authentication events client.on('auth:login', (user) => { console.log('User logged in:', user); }); client.on('auth:logout', () => { console.log('User logged out'); }); client.on('error', (error) => { console.error('SDK Error:', error); }); ``` ## 🚨 Error Handling ### Comprehensive Error Handling ```typescript import { SDKError, AuthenticationError, NetworkError, ValidationError } from '@mindmakr/gs-websdk'; async function handleApiCall() { try { const result = await client.auth.getUsers(); return result; } catch (error) { if (error instanceof AuthenticationError) { // Redirect to login window.location.href = '/login'; } else if (error instanceof NetworkError) { // Show retry option showRetryDialog(); } else if (error instanceof ValidationError) { // Show field-specific errors showValidationErrors(error.fieldErrors); } else if (error instanceof SDKError) { // Handle other SDK errors showErrorMessage(error.message); } else { // Handle unexpected errors console.error('Unexpected error:', error); } } } ``` ### Global Error Handler ```typescript // Set up global error handling client.on('error', (error) => { // Log error for monitoring console.error('SDK Error:', error); // Handle specific error types if (error instanceof AuthenticationError) { // Clear user session and redirect localStorage.removeItem('user'); window.location.href = '/login'; } else if (error instanceof NetworkError) { // Show network error notification showNotification('Network error. Please check your connection.', 'error'); } }); ``` ## 🏢 Multi-Tenant Applications ### Tenant-Specific Configuration ```typescript // Configure default tenant const client = new GuruSaaS({ baseUrl: 'https://api.yourdomain.com', defaultTenant: 'tenant-1' }); // Override tenant for specific requests const templates = await client.schema.getSchemaTemplates({}, { tenantId: 'tenant-2' }); ``` ### Tenant Switching ```typescript class TenantManager { private clients: Map<string, GuruSaaS> = new Map(); getClient(tenantId: string): GuruSaaS { if (!this.clients.has(tenantId)) { const client = new GuruSaaS({ baseUrl: 'https://api.yourdomain.com', defaultTenant: tenantId }); this.clients.set(tenantId, client); } return this.clients.get(tenantId)!; } async switchTenant(tenantId: string) { const client = this.getClient(tenantId); // Re-authenticate for new tenant if needed if (!client.isAuthenticated()) { // Redirect to login or use stored credentials } return client; } } ``` ## 📚 More Examples For more comprehensive examples, check out: - [Complete Todo App Example](../examples/todo-app-complete.tsx) - [Advanced Integration Examples](../examples/advanced-integration-examples.md) - [API Reference](./API_REFERENCE.md) for detailed SDK method documentation - [Backend API Reference](../../.context/api-reference.md) for backend API specifications ## 🆘 Support - [GitHub Issues](https://github.com/mindmakr/guru-saas/issues) - Bug reports and feature requests - [GitHub Discussions](https://github.com/mindmakr/guru-saas/discussions) - Questions and community support