# PlatformServiceMixin Best Practices Guide ## Overview This guide establishes best practices for using PlatformServiceMixin in TimeSafari components to ensure consistent, maintainable, and secure code. ## Core Principles ### 1. **Single Source of Truth** - Always use PlatformServiceMixin for database operations - Never mix legacy patterns with mixin patterns in the same component - Use mixin caching to avoid redundant database queries ### 2. **Component Context Awareness** - Always include component name in error logging - Use `this.$options.name` for consistent component identification - Implement proper error boundaries with context ### 3. **Progressive Enhancement** - Start with basic mixin methods (`$db`, `$exec`, `$one`) - Use specialized methods when available (`$getAllContacts`, `$settings`) - Leverage caching for frequently accessed data ## Implementation Patterns ### Database Operations #### ✅ **Preferred Pattern: Use Specialized Methods** ```typescript // Best: Use high-level specialized methods const contacts = await this.$getAllContacts(); const settings = await this.$settings(); const userSettings = await this.$accountSettings(did); ``` #### ✅ **Good Pattern: Use Mapped Query Methods** ```typescript // Good: Use query methods with automatic mapping const results = await this.$query( "SELECT * FROM contacts WHERE registered = ?", [true] ); ``` #### ⚠️ **Acceptable Pattern: Use Raw Database Methods** ```typescript // Acceptable: Use raw methods when specialized methods don't exist const result = await this.$db("SELECT COUNT(*) as count FROM logs"); const count = result?.values?.[0]?.[0] || 0; ``` #### ❌ **Anti-Pattern: Direct Platform Service** ```typescript // Anti-pattern: Avoid direct PlatformService usage const platformService = PlatformServiceFactory.getInstance(); const result = await platformService.dbQuery(sql, params); ``` ### Settings Management #### ✅ **Best Practice: Use Mixin Methods** ```typescript export default class MyComponent extends Vue { mixins: [PlatformServiceMixin], async loadSettings() { // ✅ Use cached settings retrieval const settings = await this.$settings(); return settings; } async saveUserPreferences(changes: Partial) { // ✅ Use specialized save method await this.$saveSettings(changes); await this.$log("User preferences saved"); } async loadAccountSettings(did: string) { // ✅ Use account-specific settings const accountSettings = await this.$accountSettings(did); return accountSettings; } } ``` #### ❌ **Anti-Pattern: Legacy Settings Access** ```typescript // Anti-pattern: Avoid legacy databaseUtil methods import * as databaseUtil from "../db/databaseUtil"; async loadSettings() { const settings = await databaseUtil.retrieveSettingsForActiveAccount(); return settings; } ``` ### Error Handling #### ✅ **Best Practice: Component-Aware Error Handling** ```typescript export default class MyComponent extends Vue { mixins: [PlatformServiceMixin], async performOperation() { try { const result = await this.$getAllContacts(); await this.$log("Operation completed successfully"); return result; } catch (error) { // ✅ Include component context in error logging await this.$logError(`[${this.$options.name}] Operation failed: ${error}`); // ✅ Provide user-friendly error handling this.$notify({ group: "alert", type: "danger", title: "Operation Failed", text: "Unable to load contacts. Please try again.", }); throw error; // Re-throw for upstream handling } } } ``` #### ❌ **Anti-Pattern: Generic Error Handling** ```typescript // Anti-pattern: Generic error handling without context try { // operation } catch (error) { console.error("Error:", error); throw error; } ``` ### Logging #### ✅ **Best Practice: Structured Logging** ```typescript export default class MyComponent extends Vue { mixins: [PlatformServiceMixin], async performDatabaseOperation() { // ✅ Log operation start with context await this.$log(`[${this.$options.name}] Starting database operation`); try { const result = await this.$getAllContacts(); // ✅ Log successful completion await this.$log(`[${this.$options.name}] Database operation completed, found ${result.length} contacts`); return result; } catch (error) { // ✅ Log errors with full context await this.$logError(`[${this.$options.name}] Database operation failed: ${error}`); throw error; } } // ✅ Use appropriate log levels async validateInput(input: string) { if (!input) { await this.$log(`[${this.$options.name}] Input validation failed: empty input`, 'warn'); return false; } return true; } } ``` ### Caching Strategies #### ✅ **Best Practice: Smart Caching Usage** ```typescript export default class MyComponent extends Vue { mixins: [PlatformServiceMixin], async loadContactsWithCaching() { // ✅ Use cached contacts (automatically managed by mixin) const contacts = await this.$contacts(); // ✅ Force refresh when needed if (this.needsFreshData) { const freshContacts = await this.$refreshContacts(); return freshContacts; } return contacts; } async updateContactAndRefresh(did: string, changes: Partial) { // ✅ Update contact and invalidate cache await this.$updateContact(did, changes); // ✅ Clear cache to ensure fresh data on next access this.$clearAllCaches(); await this.$log(`[${this.$options.name}] Contact updated and cache cleared`); } } ``` ## Security Best Practices ### Input Validation #### ✅ **Always Validate Database Inputs** ```typescript async saveContact(contact: Partial) { // ✅ Validate required fields if (!contact.did || !contact.name) { await this.$logError(`[${this.$options.name}] Invalid contact data: missing required fields`); throw new Error('Contact must have DID and name'); } // ✅ Sanitize inputs const sanitizedContact = { ...contact, name: contact.name.trim(), // Remove any potential XSS vectors notes: contact.notes?.replace(/)<[^<]*)*<\/script>/gi, '') }; return await this.$insertContact(sanitizedContact); } ``` ### Error Information Disclosure #### ✅ **Safe Error Handling** ```typescript async performSensitiveOperation(did: string) { try { // Sensitive operation const result = await this.$accountSettings(did); return result; } catch (error) { // ✅ Log full error for debugging await this.$logError(`[${this.$options.name}] Sensitive operation failed: ${error}`); // ✅ Return generic error to user throw new Error('Unable to complete operation. Please try again.'); } } ``` ### SQL Injection Prevention #### ✅ **Always Use Parameterized Queries** ```typescript // ✅ Safe: Parameterized query async findContactsByName(searchTerm: string) { return await this.$query( "SELECT * FROM contacts WHERE name LIKE ?", [`%${searchTerm}%`] ); } // ❌ Dangerous: String concatenation async findContactsByNameUnsafe(searchTerm: string) { return await this.$query( `SELECT * FROM contacts WHERE name LIKE '%${searchTerm}%'` ); } ``` ## Performance Optimization ### Database Query Optimization #### ✅ **Efficient Query Patterns** ```typescript export default class MyComponent extends Vue { mixins: [PlatformServiceMixin], async loadOptimizedData() { // ✅ Use transactions for multiple operations return await this.$withTransaction(async () => { const contacts = await this.$getAllContacts(); const settings = await this.$settings(); return { contacts, settings }; }); } async loadDataWithPagination(offset: number, limit: number) { // ✅ Use LIMIT and OFFSET for large datasets return await this.$query( "SELECT * FROM contacts ORDER BY name LIMIT ? OFFSET ?", [limit, offset] ); } } ``` ### Memory Management #### ✅ **Proper Cache Management** ```typescript export default class MyComponent extends Vue { mixins: [PlatformServiceMixin], beforeDestroy() { // ✅ Clear component caches on destroy this.$clearAllCaches(); } async handleLargeDataset() { try { // Process large dataset const largeResult = await this.$query("SELECT * FROM large_table"); // ✅ Process in chunks to avoid memory issues const chunkSize = 100; for (let i = 0; i < largeResult.length; i += chunkSize) { const chunk = largeResult.slice(i, i + chunkSize); await this.processChunk(chunk); } } finally { // ✅ Clear caches after processing large datasets this.$clearAllCaches(); } } } ``` ## Testing Strategies ### Unit Testing #### ✅ **Mock Mixin Methods** ```typescript // test/MyComponent.spec.ts import { mount } from '@vue/test-utils'; import MyComponent from '@/components/MyComponent.vue'; import { PlatformServiceMixin } from '@/utils/PlatformServiceMixin'; describe('MyComponent', () => { let wrapper; beforeEach(() => { // ✅ Mock mixin methods const mockMixin = { ...PlatformServiceMixin, methods: { ...PlatformServiceMixin.methods, $getAllContacts: jest.fn().mockResolvedValue([]), $settings: jest.fn().mockResolvedValue({}), $log: jest.fn().mockResolvedValue(undefined), $logError: jest.fn().mockResolvedValue(undefined), } }; wrapper = mount(MyComponent, { mixins: [mockMixin] }); }); it('should load contacts on mount', async () => { await wrapper.vm.loadContacts(); expect(wrapper.vm.$getAllContacts).toHaveBeenCalled(); }); }); ``` ### Integration Testing #### ✅ **Test Real Database Operations** ```typescript // test/integration/ContactsView.spec.ts import { createLocalVue, mount } from '@vue/test-utils'; import ContactsView from '@/views/ContactsView.vue'; import { PlatformServiceMixin } from '@/utils/PlatformServiceMixin'; describe('ContactsView Integration', () => { it('should perform real database operations', async () => { const wrapper = mount(ContactsView, { mixins: [PlatformServiceMixin] }); // ✅ Test real mixin functionality const contacts = await wrapper.vm.$getAllContacts(); expect(Array.isArray(contacts)).toBe(true); }); }); ``` ## Migration Checklist When migrating components to PlatformServiceMixin: ### Pre-Migration - [ ] Identify all database operations in the component - [ ] List all logging operations - [ ] Check for error handling patterns - [ ] Note any specialized database queries ### During Migration - [ ] Add PlatformServiceMixin to mixins array - [ ] Replace all database operations with mixin methods - [ ] Update logging to use mixin logging methods - [ ] Add component context to error messages - [ ] Replace settings operations with mixin methods - [ ] Update error handling to use structured patterns ### Post-Migration - [ ] Remove all legacy imports (databaseUtil, logConsoleAndDb) - [ ] Test all component functionality - [ ] Verify TypeScript compilation - [ ] Check for any remaining anti-patterns - [ ] Update component tests if needed - [ ] Run migration validation script ## Troubleshooting Common Issues ### Issue: TypeScript errors after migration **Solution**: Ensure proper type definitions and mixin interface implementation ### Issue: Methods not available on `this` **Solution**: Verify PlatformServiceMixin is properly included in mixins array ### Issue: Caching not working as expected **Solution**: Check cache TTL settings and clear cache when needed ### Issue: Database operations failing **Solution**: Verify PlatformService is properly initialized and check error logs ### Issue: Performance degradation **Solution**: Review query efficiency and cache usage patterns ## Version History - **v1.0** - Initial best practices documentation - **v1.1** - Added security and performance sections - **v1.2** - Enhanced testing strategies and troubleshooting