You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
12 KiB
12 KiB
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
// 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
// Good: Use query methods with automatic mapping
const results = await this.$query<Contact>(
"SELECT * FROM contacts WHERE registered = ?",
[true]
);
⚠️ Acceptable Pattern: Use Raw Database Methods
// 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
// Anti-pattern: Avoid direct PlatformService usage
const platformService = PlatformServiceFactory.getInstance();
const result = await platformService.dbQuery(sql, params);
Settings Management
✅ Best Practice: Use Mixin Methods
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<Settings>) {
// ✅ 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
// 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
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
// Anti-pattern: Generic error handling without context
try {
// operation
} catch (error) {
console.error("Error:", error);
throw error;
}
Logging
✅ Best Practice: Structured Logging
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
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<Contact>) {
// ✅ 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
async saveContact(contact: Partial<Contact>) {
// ✅ 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\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '')
};
return await this.$insertContact(sanitizedContact);
}
Error Information Disclosure
✅ Safe Error Handling
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
// ✅ Safe: Parameterized query
async findContactsByName(searchTerm: string) {
return await this.$query<Contact>(
"SELECT * FROM contacts WHERE name LIKE ?",
[`%${searchTerm}%`]
);
}
// ❌ Dangerous: String concatenation
async findContactsByNameUnsafe(searchTerm: string) {
return await this.$query<Contact>(
`SELECT * FROM contacts WHERE name LIKE '%${searchTerm}%'`
);
}
Performance Optimization
Database Query Optimization
✅ Efficient Query Patterns
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<Contact>(
"SELECT * FROM contacts ORDER BY name LIMIT ? OFFSET ?",
[limit, offset]
);
}
}
Memory Management
✅ Proper Cache Management
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
// 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
// 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