6.2 KiB
Database Access Pattern Clarification
Author: Matthew Raymer
Version: 1.0.0
Created: 2025-10-02 12:15:00 UTC
Last Updated: 2025-10-02 12:15:00 UTC
Current Problem: Unclear Database Access Patterns
The current hybrid scheme has conflicting database access patterns that would be difficult to implement practically.
Reality Check: What Actually Works
Option A: Host Always Provides activeDid (Recommended)
Implementation: Host application always provides activeDid to plugin
Android/Electron:
// Host queries its own database
const activeIdentity = await this.$getActiveIdentity(); // Uses host's database access
await plugin.setActiveDidFromHost(activeIdentity.activeDid);
// Plugin uses its own isolated database
await plugin.configureDatabase({
platform: 'android',
storageType: 'plugin-managed', // Plugin owns its storage
encryption: false
});
Web:
// Host queries its absurd-sql database
const activeIdentity = await this.$getActiveIdentity();
await plugin.setActiveDidFromHost(activeIdentity.activeDid);
// Plugin uses host-delegated storage
await plugin.configureDatabase({
platform: 'web',
storageType: 'host-managed' // Plugin delegates to host
});
iOS:
// Host uses its CapacitorSQLite
const activeIdentity = await this.$getActiveIdentity();
await plugin.setActiveDidFromHost(activeIdentity.activeDid);
// Plugin uses Core Data
await plugin.configureDatabase({
platform: 'ios',
storageType: 'plugin-managed' // Plugin owns Core Data
});
Option B: Plugin Polls Host Database (Backup Only)
Implementation: Plugin periodically checks for activeDid changes
Android/Electron:
// Plugin could theoretically access host database
await plugin.configureDatabase({
platform: 'android',
storageType: 'hybrid',
hostDatabaseConnection: 'shared_connection', // Requires host coordination
pollingInterval: 30000 // Check every 30 seconds
});
Issues with Option B:
- Requires careful database connection sharing
- Potential for database locking conflicts
- Race conditions during activeDid changes
- Security concerns with plugin accessing host data
Recommended Implementation: Option A
Why Option A Works Better:
- Clear Separation: Host owns identity management, plugin owns notification storage
- Security: No shared database access between host and plugin
- Reliability: Fewer moving parts, clearer failure modes
- Maintainability: Easier to test and debug
Implementation Details:
Host Application Responsibilities:
class TimeSafariNotificationService {
async initialize(): Promise<void> {
// 1. Host queries its own database for activeDid
const activeIdentity = await this.$getActiveIdentity();
// 2. Host provides activeDid to plugin immediately
await this.plugin.setActiveDidFromHost(activeIdentity.activeDid);
// 3. Host sets up change listener
this.plugin.onActiveDidChange((newActiveDid) => {
this.handleActiveDidChange(newActiveDid);
});
// 4. Plugin configures its own independent storage
await this.plugin.configureDatabase({
platform: this.getCurrentPlatform(),
storageType: 'plugin-managed'
});
}
// TimeSafari PlatformServiceMixin integration
async $updateActiveDid(newDid: string): Promise<void> {
// Update TimeSafari's active_identity table
await this.$dbExec(
"UPDATE active_identity SET activeDid = ?, lastUpdated = ? WHERE id = 1",
[newDid, new Date().toISOString()]
);
// Immediately notify plugin of change
await this.plugin.refreshActiveDidFromHost(newDid);
}
}
Plugin Responsibilities:
class DailyNotificationPlugin {
private currentActiveDid: string | null = null;
async setActiveDidFromHost(activeDid: string): Promise<void> {
this.currentActiveDid = activeDid;
await this.clearCacheForIdentityChange();
await this.refreshAuthenticationTokens();
}
async onActiveDidChange(callback: (newActiveDid: string) => Promise<void>): Promise<void> {
this.activeDidChangeCallback = callback;
}
private async refreshActiveDidFromHost(newActiveDid: string): Promise<void> {
await this.refreshAuthenticationForNewIdentity(newActiveDid);
this.activeDidChangeCallback?.(newActiveDid);
}
}
Database Isolation:
Plugin Storage (Independent):
- Cached notification content
- Authentication tokens
- Background task state
- Plugin configuration
Host Storage (TimeSafari Owns):
- active_identity table
- User settings
- Application data
- Authentication credentials
Backup Pattern for Background Tasks
For cases where plugin needs to handle activeDid changes when app is closed:
Token-Based Backup:
interface PluginConfig {
activeDid: string;
refreshToken: string; // Encrypted token that can decode new activeDid
fallbackExpiryTime: number; // When to stop using fallback
}
// Plugin can validate if activeDid hasn't expired
await plugin.validateActiveDidNotExpired(config);
Lightweight Backup Check:
// Plugin could make lightweight API call to check current activeDid
async checkCurrentActiveDid(): Promise<string | null> {
try {
const response = await this.fetch('/api/internal/active-identity', {
headers: { 'Authorization': 'Bearer ' + this.backgroundToken }
});
return response.json().activeDid;
} catch (error) {
return null; // Fail safely
}
}
Key Takeaway
The host application should ALWAYS provide activeDid to the plugin. The plugin should never directly access TimeSafari's database unless there's a very specific architectural reason (which there isn't in this case).
This approach:
- ✅ Maintains clear separation of concerns
- ✅ Avoids database access conflicts
- ✅ Ensures reliable activeDid change detection
- ✅ Simplifies implementation and testing
- ✅ Maintains security isolation
Status: Clarification complete - Option A recommended
Next Steps: Update implementation plan to use host-provided activeDid only
Impact: Simplified architecture with clearer responsibilities