/** * Platform Service Mixin for Vue Components * * Provides class-level caching of platform service instances to avoid * repeated PlatformServiceFactory.getInstance() calls throughout components. * * This mixin implements a hybrid approach combining: * - Class-level service caching for performance * - Vue composition API patterns for modern development * - Mixin pattern for easy integration with existing class components * - Enhanced utility methods for common patterns * - Robust error handling and logging * - Ultra-concise database interaction methods * * Benefits: * - Eliminates repeated PlatformServiceFactory.getInstance() calls * - Provides consistent service access pattern across components * - Improves performance with cached instances * - Maintains type safety with TypeScript * - Includes common database utility patterns * - Enhanced error handling and logging * - Ultra-concise method names for frequent operations * * @author Matthew Raymer * @version 3.0.0 * @since 2025-07-02 */ import { PlatformServiceFactory } from "@/services/PlatformServiceFactory"; import type { PlatformService } from "@/services/PlatformService"; import { mapColumnsToValues, parseJsonField } from "@/db/databaseUtil"; import { MASTER_SETTINGS_KEY } from "@/db/tables/settings"; /** * Enhanced mixin that provides cached platform service access and utility methods * * Usage: * ```typescript * @Component({ * mixins: [PlatformServiceMixin] * }) * export default class MyComponent extends Vue { * async someMethod() { * // Direct access without type assertions * const result = await this.$dbQuery('SELECT * FROM users'); * * // Utility methods for common patterns * const settings = await this.$getSettings('user123'); * } * } * ``` */ export const PlatformServiceMixin = { data() { return { _platformService: null as PlatformService | null, }; }, computed: { /** * Get the cached platform service instance * Creates and caches the instance on first access */ platformService(): PlatformService { if (!(this as any)._platformService) { (this as any)._platformService = PlatformServiceFactory.getInstance(); } return (this as any)._platformService; }, /** * Check if running on Capacitor platform */ isCapacitor(): boolean { const service = (this as any).platformService as any; return typeof service.isCapacitor === "function" ? service.isCapacitor() : false; }, /** * Check if running on web platform */ isWeb(): boolean { const service = (this as any).platformService as any; return typeof service.isWeb === "function" ? service.isWeb() : false; }, /** * Check if running on Electron platform */ isElectron(): boolean { const service = (this as any).platformService as any; return typeof service.isElectron === "function" ? service.isElectron() : false; }, /** * Get platform capabilities */ capabilities() { return (this as any).platformService.getCapabilities(); }, }, methods: { /** * Enhanced database query method with error handling * Prefixed with $ to avoid naming conflicts and improve discoverability */ async $dbQuery(sql: string, params?: unknown[]) { try { return await (this as any).platformService.dbQuery(sql, params); } catch (error) { console.error( `[${(this as any).$options.name}] Database query failed:`, { sql, params, error, }, ); throw error; } }, /** * Enhanced database execution method with error handling */ async $dbExec(sql: string, params?: unknown[]) { try { return await (this as any).platformService.dbExec(sql, params); } catch (error) { console.error( `[${(this as any).$options.name}] Database exec failed:`, { sql, params, error, }, ); throw error; } }, /** * Enhanced database single row method with error handling */ async $dbGetOneRow(sql: string, params?: unknown[]) { try { return await (this as any).platformService.dbGetOneRow(sql, params); } catch (error) { console.error( `[${(this as any).$options.name}] Database getOneRow failed:`, { sql, params, error, }, ); throw error; } }, /** * Utility method for retrieving and parsing settings * Common pattern used across many components */ async $getSettings(key: string, fallback: any = null) { try { const result = await this.$dbQuery( "SELECT * FROM settings WHERE id = ? OR accountDid = ?", [key, key], ); if (!result?.values?.length) { return fallback; } const settings = mapColumnsToValues( result.columns, result.values, )[0] as any; // Handle JSON field parsing if (settings.searchBoxes) { settings.searchBoxes = parseJsonField(settings.searchBoxes, []); } return settings; } catch (error) { console.error( `[${(this as any).$options.name}] Failed to get settings:`, { key, error, }, ); return fallback; } }, /** * Utility method for merging default and account-specific settings * Handles the common pattern of layered settings */ async $getMergedSettings( defaultKey: string, accountDid?: string, defaultFallback: any = {}, ) { try { // Get default settings const defaultSettings = await this.$getSettings( defaultKey, defaultFallback, ); // If no account DID, return defaults if (!accountDid) { return defaultSettings; } // Get account-specific overrides const accountResult = await this.$dbQuery( "SELECT * FROM settings WHERE accountDid = ?", [accountDid], ); if (!accountResult?.values?.length) { return defaultSettings; } // Map and filter non-null overrides const overrideSettings = mapColumnsToValues( accountResult.columns, accountResult.values, )[0] as any; const filteredOverrides = Object.fromEntries( Object.entries(overrideSettings).filter(([_, v]) => v !== null), ); // Merge settings with overrides taking precedence const mergedSettings = { ...defaultSettings, ...filteredOverrides }; // Handle JSON field parsing if (mergedSettings.searchBoxes) { mergedSettings.searchBoxes = parseJsonField( mergedSettings.searchBoxes, [], ); } return mergedSettings; } catch (error) { console.error( `[${(this as any).$options.name}] Failed to get merged settings:`, { defaultKey, accountDid, error, }, ); return defaultFallback; } }, /** * Utility method for safe database transactions * Handles common transaction patterns with proper error handling */ async $withTransaction(callback: () => Promise): Promise { try { await this.$dbExec("BEGIN TRANSACTION"); const result = await callback(); await this.$dbExec("COMMIT"); return result; } catch (error) { await this.$dbExec("ROLLBACK"); console.error( `[${(this as any).$options.name}] Transaction failed:`, error, ); throw error; } }, // ================================================= // ULTRA-CONCISE DATABASE METHODS (shortest names) // ================================================= /** * Ultra-short database query - just $db() * @param sql SQL query string * @param params Query parameters */ async $db(sql: string, params: unknown[] = []): Promise { return await (this as any).platformService.dbQuery(sql, params); }, /** * Ultra-short database exec - just $exec() * @param sql SQL statement string * @param params Statement parameters */ async $exec(sql: string, params: unknown[] = []): Promise { return await (this as any).platformService.dbExec(sql, params); }, /** * Ultra-short single row query - just $one() * @param sql SQL query string * @param params Query parameters */ async $one(sql: string, params: unknown[] = []): Promise { return await (this as any).platformService.dbGetOneRow(sql, params); }, // ================================================= // QUERY + MAPPING COMBO METHODS (ultimate conciseness) // ================================================= /** * Query with automatic result mapping - $query() * Combines database query + mapping in one call * @param sql SQL query string * @param params Query parameters * @returns Mapped array of results */ async $query(sql: string, params: unknown[] = []): Promise { const result = await (this as any).platformService.dbQuery(sql, params); if (!result?.columns || !result?.values) { return []; } return mapColumnsToValues(result.columns, result.values) || []; }, /** * Get first result with automatic mapping - $first() * @param sql SQL query string * @param params Query parameters * @returns First mapped result or null */ async $first(sql: string, params: unknown[] = []): Promise { const results = await (this as any).$query(sql, params); return results.length > 0 ? results[0] : null; }, // ================================================= // SPECIALIZED SHORTCUTS (common patterns) // ================================================= /** * Load all contacts in one call - $contacts() * Ultra-concise shortcut for the most common query * @returns Mapped array of all contacts */ async $contacts(): Promise { return await (this as any).$query("SELECT * FROM contacts ORDER BY name"); }, /** * Load settings with optional defaults - $settings() * @param defaults Optional default values * @returns Settings object */ async $settings(defaults: any = {}): Promise { return await (this as any).$getSettings(MASTER_SETTINGS_KEY, defaults); }, /** * Load account-specific settings - $accountSettings() * @param did DID identifier (optional, uses current active DID) * @param defaults Optional default values * @returns Merged settings object */ async $accountSettings(did?: string, defaults: any = {}): Promise { const currentDid = did || (this as any).activeDid; if (!currentDid) { return await (this as any).$settings(defaults); } return await (this as any).$getMergedSettings(MASTER_SETTINGS_KEY, currentDid, defaults); }, }, }; /** * Enhanced interface with utility methods */ export interface IPlatformServiceMixin { platformService: PlatformService; $dbQuery(sql: string, params?: unknown[]): Promise; $dbExec(sql: string, params?: unknown[]): Promise; $dbGetOneRow(sql: string, params?: unknown[]): Promise; $getSettings(key: string, fallback?: any): Promise; $getMergedSettings( defaultKey: string, accountDid?: string, defaultFallback?: any, ): Promise; $withTransaction(callback: () => Promise): Promise; isCapacitor: boolean; isWeb: boolean; isElectron: boolean; capabilities: any; } // TypeScript declaration merging to eliminate (this as any) type assertions declare module "@vue/runtime-core" { interface ComponentCustomProperties { // Core platform service access platformService: PlatformService; isCapacitor: boolean; isWeb: boolean; isElectron: boolean; capabilities: any; // Ultra-concise database methods (shortest possible names) $db(sql: string, params?: unknown[]): Promise; $exec(sql: string, params?: unknown[]): Promise; $one(sql: string, params?: unknown[]): Promise; // Query + mapping combo methods $query(sql: string, params?: unknown[]): Promise; $first(sql: string, params?: unknown[]): Promise; // Enhanced utility methods $dbQuery(sql: string, params?: unknown[]): Promise; $dbExec(sql: string, params?: unknown[]): Promise; $dbGetOneRow(sql: string, params?: unknown[]): Promise; $getSettings(key: string, defaults?: any): Promise; $getMergedSettings(key: string, did?: string, defaults?: any): Promise; $withTransaction(fn: () => Promise): Promise; // Specialized shortcuts for ultra-common patterns $contacts(): Promise; $settings(defaults?: any): Promise; $accountSettings(did?: string, defaults?: any): Promise; } }