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.
 
 
 
 
 
 

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

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

Why Option A Works Better:

  1. Clear Separation: Host owns identity management, plugin owns notification storage
  2. Security: No shared database access between host and plugin
  3. Reliability: Fewer moving parts, clearer failure modes
  4. 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