timesafari
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.
 
 
 

443 lines
13 KiB

/**
* 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<T>(callback: () => Promise<T>): Promise<T> {
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<any> {
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<any> {
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<any> {
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<any[]> {
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<any | null> {
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<any[]> {
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<any> {
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<any> {
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<any>;
$dbExec(sql: string, params?: unknown[]): Promise<any>;
$dbGetOneRow(sql: string, params?: unknown[]): Promise<any>;
$getSettings(key: string, fallback?: any): Promise<any>;
$getMergedSettings(
defaultKey: string,
accountDid?: string,
defaultFallback?: any,
): Promise<any>;
$withTransaction<T>(callback: () => Promise<T>): Promise<T>;
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<any>;
$exec(sql: string, params?: unknown[]): Promise<any>;
$one(sql: string, params?: unknown[]): Promise<any>;
// Query + mapping combo methods
$query(sql: string, params?: unknown[]): Promise<any[]>;
$first(sql: string, params?: unknown[]): Promise<any | null>;
// Enhanced utility methods
$dbQuery(sql: string, params?: unknown[]): Promise<any>;
$dbExec(sql: string, params?: unknown[]): Promise<any>;
$dbGetOneRow(sql: string, params?: unknown[]): Promise<any>;
$getSettings(key: string, defaults?: any): Promise<any>;
$getMergedSettings(key: string, did?: string, defaults?: any): Promise<any>;
$withTransaction<T>(fn: () => Promise<T>): Promise<T>;
// Specialized shortcuts for ultra-common patterns
$contacts(): Promise<any[]>;
$settings(defaults?: any): Promise<any>;
$accountSettings(did?: string, defaults?: any): Promise<any>;
}
}